From 99c730e4c6a71bb18876e55c540344f784b3ab79 Mon Sep 17 00:00:00 2001 From: DanCardin Date: Wed, 31 Jan 2024 20:22:12 -0500 Subject: [PATCH] feat: Add support for Snowflake schemas. --- .github/workflows/test.yml | 6 + README.md | 225 +++++----- poetry.lock | 404 +++++++++++++++--- pyproject.toml | 15 +- .../alembic/schema.py | 25 +- src/sqlalchemy_declarative_extensions/api.py | 11 +- .../dialects/postgresql/query.py | 2 +- .../dialects/query.py | 8 + .../dialects/snowflake/__init__.py | 0 .../dialects/snowflake/query.py | 28 ++ .../dialects/sqlite/query.py | 7 + .../schema/__init__.py | 2 + .../schema/compare.py | 7 + .../schema/ddl.py | 22 +- .../sqlalchemy.py | 3 +- tests/conftest.py | 17 + tests/schema/test_drop.py | 45 ++ tests/schema/test_sqlalchemy.py | 14 +- tests/view/test_sqlalchemy.py | 16 +- 19 files changed, 654 insertions(+), 203 deletions(-) create mode 100644 src/sqlalchemy_declarative_extensions/dialects/snowflake/__init__.py create mode 100644 src/sqlalchemy_declarative_extensions/dialects/snowflake/query.py create mode 100644 tests/schema/test_drop.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1954ba8..f55027c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,7 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Set up Python uses: actions/setup-python@v4 with: @@ -56,6 +57,11 @@ jobs: - name: Install dependencies run: poetry install -E parse + - name: Install snowflake + if: ${{ matrix.sqlalchemy-version == '1.4.0' }} + run: | + poetry run pip install 'snowflake-sqlalchemy' + - name: Install specific sqlalchemy version run: | poetry run pip install 'sqlalchemy~=${{ matrix.sqlalchemy-version }}' diff --git a/README.md b/README.md index 79b49d3..51609a5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # SQLAlchemy Declarative Extensions -[![Actions Status](https://github.com/dancardin/sqlalchemy-declarative-extensions/workflows/test/badge.svg)](https://github.com/dancardin/sqlalchemy-declarative-extensions/actions) [![Coverage Status](https://coveralls.io/repos/github/DanCardin/sqlalchemy-declarative-extensions/badge.svg?branch=main)](https://coveralls.io/github/DanCardin/sqlalchemy-declarative-extensions?branch=main) [![Documentation Status](https://readthedocs.org/projects/sqlalchemy-declarative-extensions/badge/?version=latest)](https://sqlalchemy-declarative-extensions.readthedocs.io/en/latest/?badge=latest) +[![Actions Status](https://github.com/dancardin/sqlalchemy-declarative-extensions/workflows/test/badge.svg)](https://github.com/dancardin/sqlalchemy-declarative-extensions/actions) +[![Coverage Status](https://coveralls.io/repos/github/DanCardin/sqlalchemy-declarative-extensions/badge.svg?branch=main)](https://coveralls.io/github/DanCardin/sqlalchemy-declarative-extensions?branch=main) +[![Documentation Status](https://readthedocs.org/projects/sqlalchemy-declarative-extensions/badge/?version=latest)](https://sqlalchemy-declarative-extensions.readthedocs.io/en/latest/?badge=latest) -See the full documentation [here](https://sqlalchemy-declarative-extensions.readthedocs.io/en/latest/). +See the full documentation +[here](https://sqlalchemy-declarative-extensions.readthedocs.io/en/latest/). Adds extensions to SQLAlchemy (and/or Alembic) which allows declaratively -stating the existence of additional kinds of objects about your database -not natively supported by SQLAlchemy/Alembic. +stating the existence of additional kinds of objects about your database not +natively supported by SQLAlchemy/Alembic. This includes: @@ -23,8 +26,9 @@ The primary function(s) of this library include: - Registering onto the SQLAlchemy event system such that `metadata.create_all` creates these objects. -- (Optionally) Registers into Alembic such that `alembic revision --autogenerate` - automatically creates/updates/deletes declared objects. +- (Optionally) Registers into Alembic such that + `alembic revision --autogenerate` automatically creates/updates/deletes + declared objects. ## Kitchen Sink Example (using all available features) @@ -103,18 +107,19 @@ class HighFoo: __view__ = select(Foo.__table__).where(Foo.__table__.c.id >= 10) ``` -Note, there is also support for declaring objects directly through the `MetaData` for -users not using sqlalchemy's declarative API. +Note, there is also support for declaring objects directly through the +`MetaData` for users not using sqlalchemy's declarative API. ## Event Registration -By default, the above example does not automatically do anything. Depending on the context, -you can use one of two registration hooks: `register_sqlalchemy_events` or `register_alembic_events`. +By default, the above example does not automatically do anything. Depending on +the context, you can use one of two registration hooks: +`register_sqlalchemy_events` or `register_alembic_events`. ### `register_sqlalchemy_events` -This registers events in SQLAlchemy's event system such that a `metadata.create_all(...)` call -will create the declared database objects. +This registers events in SQLAlchemy's event system such that a +`metadata.create_all(...)` call will create the declared database objects. ```python from sqlalchemy_declarative_extensions import register_sqlalchemy_events @@ -125,18 +130,21 @@ register_sqlalchemy_events(metadata) register_sqlalchemy_events(metadata, schemas=False, roles=False, grants=False, rows=False) ``` -All object types are opt in, and should be explicitly included in order to get registered. +All object types are opt in, and should be explicitly included in order to get +registered. -Practically, this is to avoid issues with testing. In **most** cases the `metadata.create_all` call -will be run in tests, a context where it's more likely that you dont **need** grants or grants, -and where parallel test execution could lead to issues with role or schema creation, depending -on your setup. +Practically, this is to avoid issues with testing. In **most** cases the +`metadata.create_all` call will be run in tests, a context where it's more +likely that you dont **need** grants or grants, and where parallel test +execution could lead to issues with role or schema creation, depending on your +setup. ### `register_alembic_events` -This registers comparators in Alembic's registration system such that an `alembic revision --autogenerate` -command will diff the existing database state against the declared database objects, and issue -statements to create/update/delete objects in order to match the declared state. +This registers comparators in Alembic's registration system such that an +`alembic revision --autogenerate` command will diff the existing database state +against the declared database objects, and issue statements to +create/update/delete objects in order to match the declared state. ```python # alembic's `env.py` @@ -149,110 +157,129 @@ register_sqlalchemy_events(schemas=True, roles=True, grants=True, rows=True) All object types are opt out but can be excluded. -In contrast to `register_sqlalchemy_events`, it's much more likely that you're declaring most of these -object types in order to have alembic track them +In contrast to `register_sqlalchemy_events`, it's much more likely that you're +declaring most of these object types in order to have alembic track them ## Database support -In principle, this library **can** absolutely support any database supported by SQLAlchemy, -and capable of being introspected enough to support detection of different kinds of objects. - -As you can see below, in reality the existence of implementations are going to be purely driven by actual -usage. The current maintainer(s) primarily use PostgreSQL and as such individual features for -other databases will either suffer or lack implementation. - -| | Postgres | MySQL | SQLite | -|---------------|----------|-------|--------| -| Schema | ✓ | | ✓ | -| View | ✓ | ✓ | ✓ | -| Role | ✓ | | N/A | -| Grant | ✓ | | N/A | -| Default Grant | ✓ | | N/A | -| Function | ✓ | * | | -| Trigger | ✓ | * | | -| Row (data) | ✓ | ✓ | ✓ | -| "Audit Table" | ✓ | | | - -The astrisks above note pending or provisional support. The level of expertise in each dialects' -particular behaviors is not uniform, and deciding on the correct behavior for those dialects -will require users to submit issues/fixes! - -Supporting a new dialect **can** be as simple as providing the dialect-dispatched implementations -for detecting existing objects of the given type. Very much the intent is that once a given object -type is supported at all, the comparison infrastructure for that type should make it straightforward -to support other dialects. At the end of the day, this library is primarily producing SQL statements, -so in theory any dialect supporting a given object type should be able to be supported. - -In such cases (like Grants/Roles) that different dialects support wildly different -options/syntax, there are also patterns for defining dialect-specific objects, to mediate -any additional differences. +In principle, this library **can** absolutely support any database supported by +SQLAlchemy, and capable of being introspected enough to support detection of +different kinds of objects. + +As you can see below, in reality the existence of implementations are going to +be purely driven by actual usage. The current maintainer(s) primarily use +PostgreSQL and as such individual features for other databases will either +suffer or lack implementation. + +| | Postgres | MySQL | SQLite | Snowflake | +| ------------- | -------- | ----- | ------ | --------- | +| Schema | ✓ | N/A | ✓ | ✓ | +| View | ✓ | ✓ | ✓ | | +| Role | ✓ | | N/A | | +| Grant | ✓ | | N/A | | +| Default Grant | ✓ | | N/A | | +| Function | ✓ | * | | | +| Trigger | ✓ | * | | | +| Row (data) | ✓ | ✓ | ✓ | ✓ | +| "Audit Table" | ✓ | | | | + +**note** "Row" is implemented with pure SQLAlchemy concepts, so should work for +any dialect that you can use SQLAlchemy to connect to. + +The astrisks above note pending or provisional support through basic test cases. +The level of expertise in each dialects' particular behaviors is not uniform, +and deciding on the correct behavior for those dialects will require users to +submit issues/fixes! + +Supporting a new dialect **can** be as simple as providing the +dialect-dispatched implementations for detecting existing objects of the given +type. Very much the intent is that once a given object type is supported at all, +the comparison infrastructure for that type should make it straightforward to +support other dialects. At the end of the day, this library is primarily +producing SQL statements, so in theory any dialect supporting a given object +type should be able to be supported. + +In such cases (like Grants/Roles) that different dialects support wildly +different options/syntax, there are also patterns for defining dialect-specific +objects, to mediate any additional differences. ## Alembic-utils -[Alembic Utils](https://github.com/olirice/alembic_utils) is the primary library against which -this library can be compared. At time of writing, **most** (but not all) object types supported -by alembic-utils are supported by this library. One might begin to question when to use which library. +[Alembic Utils](https://github.com/olirice/alembic_utils) is the primary library +against which this library can be compared. At time of writing, **most** (but +not all) object types supported by alembic-utils are supported by this library. +One might begin to question when to use which library. -Below is a list of points on which the two libraries diverge. But note that you **can** certainly -use both in tandem! It doesn't need to be one or the other, and certainly for any object types -which do not overlap, you might **need** to use both. +Below is a list of points on which the two libraries diverge. But note that you +**can** certainly use both in tandem! It doesn't need to be one or the other, +and certainly for any object types which do not overlap, you might **need** to +use both. - Database Support - Alembic Utils seems to explicitly only support PostgreSQL. - - This library is designed to support any dialect (in theory). Certainly PostgreSQL - is **best** supported, but there does exist support for specific kinds of objects - to varying levels of support for SQLite and MySQL, so far. + - This library is designed to support any dialect (in theory). Certainly + PostgreSQL is **best** supported, but there does exist support for specific + kinds of objects to varying levels of support for SQLite and MySQL, so far. - Architecture - - Alembic Utils is directly tied to Alembic and does not support SQLAlchemy's `MetaData.create_all`. - It's also the responsibility of the user to discover/register objects in alembic. + - Alembic Utils is directly tied to Alembic and does not support SQLAlchemy's + `MetaData.create_all`. It's also the responsibility of the user to + discover/register objects in alembic. - - This library **depends** only on SQLAlchemy, although it also supports alembic. Support for - `MetaData.create_all` can be important for creating all object types in tests. It also - is designed such that objects are registered on the `MetaData` itself, so there is no need for - any specific discovery phase. + - This library **depends** only on SQLAlchemy, although it also supports + alembic. Support for `MetaData.create_all` can be important for creating all + object types in tests. It also is designed such that objects are registered + on the `MetaData` itself, so there is no need for any specific discovery + phase. - Scope - - Alembic Utils declares specific, individual objects. I.e. you instantiate one specific `PGGrantTable` - or `PGView` instance and Alembic know knows you want that object to be created. It cannot drop - objects it is not already aware of. + - Alembic Utils declares specific, individual objects. I.e. you instantiate + one specific `PGGrantTable` or `PGView` instance and Alembic know knows you + want that object to be created. It cannot drop objects it is not already + aware of. - - This library declares ths objects the system as a whole expects to exist. Similar to Alembic's - behavior on tables, it will (by default) detect any **undeclared** objects that should not exist - and drop them. That means, you can rely on this object to ensure the state of your migrations - matches the state of your database exactly. + - This library declares ths objects the system as a whole expects to exist. + Similar to Alembic's behavior on tables, it will (by default) detect any + **undeclared** objects that should not exist and drop them. That means, you + can rely on this object to ensure the state of your migrations matches the + state of your database exactly. - Migration history - - Alembic Utils imports and references its own objects in your migrations history. This can be - unfortunate, in that it deeply ties your migrations history to alembic-utils. + - Alembic Utils imports and references its own objects in your migrations + history. This can be unfortunate, in that it deeply ties your migrations + history to alembic-utils. - (In fact, this can be a sticking point, trying to convert **away** from `alembic_utils`, because it - will attempt to drop all the (e.g `PGView`) instances previously created when we replaced it with - this library.) + (In fact, this can be a sticking point, trying to convert **away** from + `alembic_utils`, because it will attempt to drop all the (e.g `PGView`) + instances previously created when we replaced it with this library.) - - This library, by contrast, prefers to emit the raw SQL of the operation into your migration. - That means you know the exact commands that will execute in your migration, which can be helpful - in debugging failure. It also means, if at any point you decide to stop use of the library - (or pause a given type of object, due to a bug), you can! This library's behaviors are primarily - very much `--autogenerate`-time only. + - This library, by contrast, prefers to emit the raw SQL of the operation into + your migration. That means you know the exact commands that will execute in + your migration, which can be helpful in debugging failure. It also means, if + at any point you decide to stop use of the library (or pause a given type of + object, due to a bug), you can! This library's behaviors are primarily very + much `--autogenerate`-time only. - Abstraction Level - - Alembic Utils appears to define a very "literal" interface (for example, `PGView` accepts - the whole view definition as a raw literal string). - - - This library tries to, as much as possible, provide a more abstracted interface that enables - more compatibility with SQLAlchemy (For example `View` accepts SQLAlchemy objects which can - be coerced into a `SELECT`). It also tends towards "builder" interfaces which progressively produce - a object (Take a look at the `DefaultGrant` above, for an example of where that's helpful). - -A separate note on conversion/compatibility. Where possible, this library tries to support alembic-utils -native objects as stand-ins for the objects defined in this library. For example, `alembic_utils.pg_view.PGView` -can be declared instead of a `sqlalchemy_declarative_extensions.View`, and we will internally -coerce it into the appropriate type. Hopefully this eases any transitional costs, or -issues using one or the other library. + - Alembic Utils appears to define a very "literal" interface (for example, + `PGView` accepts the whole view definition as a raw literal string). + + - This library tries to, as much as possible, provide a more abstracted + interface that enables more compatibility with SQLAlchemy (For example + `View` accepts SQLAlchemy objects which can be coerced into a `SELECT`). It + also tends towards "builder" interfaces which progressively produce a object + (Take a look at the `DefaultGrant` above, for an example of where that's + helpful). + +A separate note on conversion/compatibility. Where possible, this library tries +to support alembic-utils native objects as stand-ins for the objects defined in +this library. For example, `alembic_utils.pg_view.PGView` can be declared +instead of a `sqlalchemy_declarative_extensions.View`, and we will internally +coerce it into the appropriate type. Hopefully this eases any transitional +costs, or issues using one or the other library. diff --git a/poetry.lock b/poetry.lock index 273aa31..f398e65 100644 --- a/poetry.lock +++ b/poetry.lock @@ -57,6 +57,17 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} +[[package]] +name = "asn1crypto" +version = "1.5.1" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +optional = false +python-versions = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] + [[package]] name = "backports-zoneinfo" version = "0.2.1" @@ -396,58 +407,105 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.3" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a"}, - {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b"}, - {file = "cryptography-42.0.3-cp37-abi3-win32.whl", hash = "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5"}, - {file = "cryptography-42.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54"}, - {file = "cryptography-42.0.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65"}, - {file = "cryptography-42.0.3-cp39-abi3-win32.whl", hash = "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3"}, - {file = "cryptography-42.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd"}, - {file = "cryptography-42.0.3.tar.gz", hash = "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} +cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "duckdb" +version = "0.10.0" +description = "DuckDB in-process database" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "duckdb-0.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bd0ffb3fddef0f72a150e4d76e10942a84a1a0447d10907df1621b90d6668060"}, + {file = "duckdb-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3d709d5c7c1a12b5e10d0b05fa916c670cd2b50178e3696faa0cc16048a1745"}, + {file = "duckdb-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9114aa22ec5d591a20ce5184be90f49d8e5b5348ceaab21e102c54560d07a5f8"}, + {file = "duckdb-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a37877efadf39caf7cadde0f430fedf762751b9c54750c821e2f1316705a21"}, + {file = "duckdb-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87cbc9e1d9c3fc9f14307bea757f99f15f46843c0ab13a6061354410824ed41f"}, + {file = "duckdb-0.10.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0bfec79fed387201550517d325dff4fad2705020bc139d936cab08b9e845662"}, + {file = "duckdb-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5622134d2d9796b15e09de810e450859d4beb46d9b861357ec9ae40a61b775c"}, + {file = "duckdb-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:089ee8e831ccaef1b73fc89c43b661567175eed0115454880bafed5e35cda702"}, + {file = "duckdb-0.10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a05af63747f1d7021995f0811c333dee7316cec3b06c0d3e4741b9bdb678dd21"}, + {file = "duckdb-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:072d6eba5d8a59e0069a8b5b4252fed8a21f9fe3f85a9129d186a39b3d0aea03"}, + {file = "duckdb-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a77b85668f59b919042832e4659538337f1c7f197123076c5311f1c9cf077df7"}, + {file = "duckdb-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a666f1d2da65d03199a977aec246920920a5ea1da76b70ae02bd4fb1ffc48c"}, + {file = "duckdb-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ec76a4262b783628d26612d184834852d9c92fb203e91af789100c17e3d7173"}, + {file = "duckdb-0.10.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:009dd9d2cdbd3b061a9efbdfc79f2d1a8377bcf49f1e5f430138621f8c083a6c"}, + {file = "duckdb-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:878f06766088090dad4a2e5ee0081555242b2e8dcb29415ecc97e388cf0cf8d8"}, + {file = "duckdb-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:713ff0a1fb63a6d60f454acf67f31656549fb5d63f21ac68314e4f522daa1a89"}, + {file = "duckdb-0.10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9c0ee450dfedfb52dd4957244e31820feef17228da31af6d052979450a80fd19"}, + {file = "duckdb-0.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ff79b2ea9994398b545c0d10601cd73565fbd09f8951b3d8003c7c5c0cebc7cb"}, + {file = "duckdb-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6bdf1aa71b924ef651062e6b8ff9981ad85bec89598294af8a072062c5717340"}, + {file = "duckdb-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0265bbc8216be3ced7b377ba8847128a3fc0ef99798a3c4557c1b88e3a01c23"}, + {file = "duckdb-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d418a315a07707a693bd985274c0f8c4dd77015d9ef5d8d3da4cc1942fd82e0"}, + {file = "duckdb-0.10.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2828475a292e68c71855190b818aded6bce7328f79e38c04a0c75f8f1c0ceef0"}, + {file = "duckdb-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c3aaeaae2eba97035c65f31ffdb18202c951337bf2b3d53d77ce1da8ae2ecf51"}, + {file = "duckdb-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c51790aaaea97d8e4a58a114c371ed8d2c4e1ca7cbf29e3bdab6d8ccfc5afc1e"}, + {file = "duckdb-0.10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8af1ae7cc77a12206b6c47ade191882cc8f49f750bb3e72bb86ac1d4fa89926a"}, + {file = "duckdb-0.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa4f7e8e8dc0e376aeb280b83f2584d0e25ec38985c27d19f3107b2edc4f4a97"}, + {file = "duckdb-0.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ae942a79fad913defa912b56483cd7827a4e7721f4ce4bc9025b746ecb3c89"}, + {file = "duckdb-0.10.0-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01b57802898091455ca2a32c1335aac1e398da77c99e8a96a1e5de09f6a0add9"}, + {file = "duckdb-0.10.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52e1ad4a55fa153d320c367046b9500578192e01c6d04308ba8b540441736f2c"}, + {file = "duckdb-0.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:904c47d04095af745e989c853f0bfc0776913dfc40dfbd2da7afdbbb5f67fed0"}, + {file = "duckdb-0.10.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:184ae7ea5874f3b8fa51ab0f1519bdd088a0b78c32080ee272b1d137e2c8fd9c"}, + {file = "duckdb-0.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd33982ecc9bac727a032d6cedced9f19033cbad56647147408891eb51a6cb37"}, + {file = "duckdb-0.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f59bf0949899105dd5f8864cb48139bfb78454a8c017b8258ba2b5e90acf7afc"}, + {file = "duckdb-0.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:395f3b18948001e35dceb48a4423d574e38656606d033eef375408b539e7b076"}, + {file = "duckdb-0.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b8eb2b803be7ee1df70435c33b03a4598cdaf676cd67ad782b288dcff65d781"}, + {file = "duckdb-0.10.0-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31b2ddd331801064326c8e3587a4db8a31d02aef11332c168f45b3bd92effb41"}, + {file = "duckdb-0.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c8b89e76a041424b8c2026c5dc1f74b53fbbc6c6f650d563259885ab2e7d093d"}, + {file = "duckdb-0.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:79084a82f16c0a54f6bfb7ded5600400c2daa90eb0d83337d81a56924eaee5d4"}, + {file = "duckdb-0.10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:79799b3a270dcd9070f677ba510f1e66b112df3068425691bac97c5e278929c7"}, + {file = "duckdb-0.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8fc394bfe3434920cdbcfbdd0ac3ba40902faa1dbda088db0ba44003a45318a"}, + {file = "duckdb-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c116605551b4abf5786243a59bcef02bd69cc51837d0c57cafaa68cdc428aa0c"}, + {file = "duckdb-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3191170c3b0a43b0c12644800326f5afdea00d5a4621d59dbbd0c1059139e140"}, + {file = "duckdb-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fee69a50eb93c72dc77e7ab1fabe0c38d21a52c5da44a86aa217081e38f9f1bd"}, + {file = "duckdb-0.10.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5f449e87dacb16b0d145dbe65fa6fdb5a55b2b6911a46d74876e445dd395bac"}, + {file = "duckdb-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4487d0df221b17ea4177ad08131bc606b35f25cfadf890987833055b9d10cdf6"}, + {file = "duckdb-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c099ae2ff8fe939fda62da81704f91e2f92ac45e48dc0e37c679c9d243d01e65"}, + {file = "duckdb-0.10.0.tar.gz", hash = "sha256:c02bcc128002aa79e3c9d89b9de25e062d1096a8793bc0d7932317b7977f6845"}, +] + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -476,6 +534,27 @@ files = [ [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] +[[package]] +name = "fakesnow" +version = "0.9.2" +description = "Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally." +optional = false +python-versions = ">=3.9" +files = [ + {file = "fakesnow-0.9.2-py3-none-any.whl", hash = "sha256:87ac0ea93a302f08bbeaf61ab822283fc200a073f9ad329cc77125970b0296ba"}, + {file = "fakesnow-0.9.2.tar.gz", hash = "sha256:ac9da7e7bf6018ac4ffc22101f724f9b9f9b2017ce5c5631d1ddaa117c28abef"}, +] + +[package.dependencies] +duckdb = ">=0.10.0,<0.11.0" +pyarrow = "*" +snowflake-connector-python = "*" +sqlglot = ">=21.1.0,<21.2.0" + +[package.extras] +dev = ["black (>=23.9,<24.0)", "build (>=1.0,<2.0)", "pre-commit (>=3.4,<4.0)", "pytest (>=7.4,<8.0)", "ruff (>=0.1.6,<0.2.0)", "snowflake-connector-python[pandas,secure-local-storage]", "twine (>=4.0,<5.0)"] +notebook = ["duckdb-engine", "ipykernel", "jupysql", "snowflake-sqlalchemy"] + [[package]] name = "filelock" version = "3.13.1" @@ -783,6 +862,51 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + [[package]] name = "packaging" version = "23.2" @@ -818,18 +942,18 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" +version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" @@ -948,6 +1072,54 @@ files = [ {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] +[[package]] +name = "pyarrow" +version = "15.0.0" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyarrow-15.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0a524532fd6dd482edaa563b686d754c70417c2f72742a8c990b322d4c03a15d"}, + {file = "pyarrow-15.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60a6bdb314affa9c2e0d5dddf3d9cbb9ef4a8dddaa68669975287d47ece67642"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66958fd1771a4d4b754cd385835e66a3ef6b12611e001d4e5edfcef5f30391e2"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f500956a49aadd907eaa21d4fff75f73954605eaa41f61cb94fb008cf2e00c6"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6f87d9c4f09e049c2cade559643424da84c43a35068f2a1c4653dc5b1408a929"}, + {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85239b9f93278e130d86c0e6bb455dcb66fc3fd891398b9d45ace8799a871a1e"}, + {file = "pyarrow-15.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b8d43e31ca16aa6e12402fcb1e14352d0d809de70edd185c7650fe80e0769e3"}, + {file = "pyarrow-15.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:fa7cd198280dbd0c988df525e50e35b5d16873e2cdae2aaaa6363cdb64e3eec5"}, + {file = "pyarrow-15.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8780b1a29d3c8b21ba6b191305a2a607de2e30dab399776ff0aa09131e266340"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0ec198ccc680f6c92723fadcb97b74f07c45ff3fdec9dd765deb04955ccf19"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036a7209c235588c2f07477fe75c07e6caced9b7b61bb897c8d4e52c4b5f9555"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2bd8a0e5296797faf9a3294e9fa2dc67aa7f10ae2207920dbebb785c77e9dbe5"}, + {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e8ebed6053dbe76883a822d4e8da36860f479d55a762bd9e70d8494aed87113e"}, + {file = "pyarrow-15.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d53a9d1b2b5bd7d5e4cd84d018e2a45bc9baaa68f7e6e3ebed45649900ba99"}, + {file = "pyarrow-15.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9950a9c9df24090d3d558b43b97753b8f5867fb8e521f29876aa021c52fda351"}, + {file = "pyarrow-15.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:003d680b5e422d0204e7287bb3fa775b332b3fce2996aa69e9adea23f5c8f970"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f75fce89dad10c95f4bf590b765e3ae98bcc5ba9f6ce75adb828a334e26a3d40"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca9cb0039923bec49b4fe23803807e4ef39576a2bec59c32b11296464623dc2"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ed5a78ed29d171d0acc26a305a4b7f83c122d54ff5270810ac23c75813585e4"}, + {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6eda9e117f0402dfcd3cd6ec9bfee89ac5071c48fc83a84f3075b60efa96747f"}, + {file = "pyarrow-15.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a3a6180c0e8f2727e6f1b1c87c72d3254cac909e609f35f22532e4115461177"}, + {file = "pyarrow-15.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:19a8918045993349b207de72d4576af0191beef03ea655d8bdb13762f0cd6eac"}, + {file = "pyarrow-15.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0ec076b32bacb6666e8813a22e6e5a7ef1314c8069d4ff345efa6246bc38593"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5db1769e5d0a77eb92344c7382d6543bea1164cca3704f84aa44e26c67e320fb"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2617e3bf9df2a00020dd1c1c6dce5cc343d979efe10bc401c0632b0eef6ef5b"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:d31c1d45060180131caf10f0f698e3a782db333a422038bf7fe01dace18b3a31"}, + {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:c8c287d1d479de8269398b34282e206844abb3208224dbdd7166d580804674b7"}, + {file = "pyarrow-15.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:07eb7f07dc9ecbb8dace0f58f009d3a29ee58682fcdc91337dfeb51ea618a75b"}, + {file = "pyarrow-15.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:47af7036f64fce990bb8a5948c04722e4e3ea3e13b1007ef52dfe0aa8f23cf7f"}, + {file = "pyarrow-15.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93768ccfff85cf044c418bfeeafce9a8bb0cee091bd8fd19011aff91e58de540"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6ee87fd6892700960d90abb7b17a72a5abb3b64ee0fe8db6c782bcc2d0dc0b4"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:001fca027738c5f6be0b7a3159cc7ba16a5c52486db18160909a0831b063c4e4"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:d1c48648f64aec09accf44140dccb92f4f94394b8d79976c426a5b79b11d4fa7"}, + {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:972a0141be402bb18e3201448c8ae62958c9c7923dfaa3b3d4530c835ac81aed"}, + {file = "pyarrow-15.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:f01fc5cf49081426429127aa2d427d9d98e1cb94a32cb961d583a70b7c4504e6"}, + {file = "pyarrow-15.0.0.tar.gz", hash = "sha256:876858f549d540898f927eba4ef77cd549ad8d24baa3207cf1b72e5788b50e83"}, +] + +[package.dependencies] +numpy = ">=1.16.6,<2" + [[package]] name = "pycparser" version = "2.21" @@ -1069,6 +1241,23 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pymysql" version = "1.1.0" @@ -1087,6 +1276,24 @@ cryptography = {version = "*", optional = true, markers = "extra == \"rsa\""} ed25519 = ["PyNaCl (>=1.4.0)"] rsa = ["cryptography"] +[[package]] +name = "pyopenssl" +version = "23.3.0" +description = "Python wrapper module around the OpenSSL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"}, + {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"}, +] + +[package.dependencies] +cryptography = ">=41.0.5,<42" + +[package.extras] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + [[package]] name = "pytest" version = "8.0.0" @@ -1195,6 +1402,17 @@ typing-extensions = "*" [package.extras] test = ["pytest"] +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1242,6 +1460,76 @@ files = [ {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] +[[package]] +name = "snowflake-connector-python" +version = "3.7.0" +description = "Snowflake Connector for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "snowflake-connector-python-3.7.0.tar.gz", hash = "sha256:b2bfaec64059307b08caadad40214d488fefb4a23fcd7553ac75f5ea758a9169"}, + {file = "snowflake_connector_python-3.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f38070af24d15cd103d565b63b08c5eac3bdf72ad06ad27cd98c46359cb4bee2"}, + {file = "snowflake_connector_python-3.7.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f8f3912699030291fd82d75321cda44205c9f8fb27841ffbaaf6d3dc4065b798"}, + {file = "snowflake_connector_python-3.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7ac1190c6ca48297429f0fb6515b54e3fd3bceb1b72fce7b59097044a9e98e0"}, + {file = "snowflake_connector_python-3.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57deaa28baa601b64c1ae5a5c75260ab1c6a22bd07a8d8c7ac785c8deb1c556e"}, + {file = "snowflake_connector_python-3.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:715635ed5b6e5ef8de659fc336c1b89296fe72fdec180c40915c10df885c8082"}, + {file = "snowflake_connector_python-3.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d51f3a8912fcc5169731d2b42262087e8a6da20f7344dd001ed97fbdf6ff972c"}, + {file = "snowflake_connector_python-3.7.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:46bfa243875eff9c6dfe1afc26f2034b00ac6eb9f77010b2949a174c38a59722"}, + {file = "snowflake_connector_python-3.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7004ccfe3c16075d33b0440b4d5241a50156bbc5dcbf11dec61674d0ac830f46"}, + {file = "snowflake_connector_python-3.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee9e6a0a94e0ac1f15fa93c0f61f6e930240280bd043f61216d942e837beb7f"}, + {file = "snowflake_connector_python-3.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:b545fd11c9bd200681e182cf46bb4cbc8250ca6acc41fbea749799a2b23f574f"}, + {file = "snowflake_connector_python-3.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:258541df8ba90201ce6f7c4ae9f59e3a9f585ed30fbbaafd207e0774104cf6dc"}, + {file = "snowflake_connector_python-3.7.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:e548642913f7d0ef9d5a35c69c7a8308cbab8fe255fdc3c9f7e18c71e52a0c2e"}, + {file = "snowflake_connector_python-3.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639d09de40c014c7ab0308f622bd1d29a9b9dd05c0ced2d858da31323fa16bda"}, + {file = "snowflake_connector_python-3.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9cf62665ee47c7ec8c18ae554a31c72cacf1cef4b42d55cfbdbae4b5ddb3f2"}, + {file = "snowflake_connector_python-3.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad1d0e339cadb5ba79d24783c39ba21a63e2159f0d3d9540da0168f97043904c"}, + {file = "snowflake_connector_python-3.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d8e4d0fad8b00b55bc99035ad2c54d9aa3ca8495f7dfcce736a961b5dbd1d9f"}, + {file = "snowflake_connector_python-3.7.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:fc3e95d4c99472444ffda35b9bbfe4cd4c775279c7eca579f1eee9d8d2ec1e2a"}, + {file = "snowflake_connector_python-3.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f93a5861333c2f87ecd1fea34a0fae35c12c196e86fa75c2dd89741e83f2d82"}, + {file = "snowflake_connector_python-3.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdf0fe7d77e02949a8a2a7d365217b822bcaf2fc9541095a241116576458568"}, + {file = "snowflake_connector_python-3.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:1ec29946b224d8089070477f60ffe58923433d8c2308b6403684500e85c37699"}, + {file = "snowflake_connector_python-3.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f945c512383a8b5f1d2404c40d20e0c915ba3f0ac01983f2e43987d6eecda02"}, + {file = "snowflake_connector_python-3.7.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:574cf5be3c61a6ea421ac9710ac791a80f6dfcc53986ab81e68d1085dad79dab"}, + {file = "snowflake_connector_python-3.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb8168458e5d23a0ba4d4e0a276bbd477ddd26d35c554f2c3c64cfe29622499a"}, + {file = "snowflake_connector_python-3.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecf8f520692653775f51307140d326b53a51e338d67dc522b1d376b51b12d14e"}, + {file = "snowflake_connector_python-3.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:5ed928055ed40da22b2d6bdde62eee5068c352f66415e1c9aee7c45eb67d39cb"}, +] + +[package.dependencies] +asn1crypto = ">0.24.0,<2.0.0" +certifi = ">=2017.4.17" +cffi = ">=1.9,<2.0.0" +charset-normalizer = ">=2,<4" +cryptography = ">=3.1.0,<42.0.0" +filelock = ">=3.5,<4" +idna = ">=2.5,<4" +packaging = "*" +platformdirs = ">=2.6.0,<4.0.0" +pyjwt = "<3.0.0" +pyOpenSSL = ">=16.2.0,<24.0.0" +pytz = "*" +requests = "<3.0.0" +sortedcontainers = ">=2.4.0" +tomlkit = "*" +typing-extensions = ">=4.3,<5" +urllib3 = {version = ">=1.21.1,<2.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +development = ["Cython", "coverage", "more-itertools", "numpy (<1.27.0)", "pendulum (!=2.1.1)", "pexpect", "pytest (<7.5.0)", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist", "pytzdata"] +pandas = ["pandas (>=1.0.0,<2.2.0)", "pyarrow"] +secure-local-storage = ["keyring (!=16.1.0,<25.0.0)"] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + [[package]] name = "sqlalchemy" version = "2.0.27" @@ -1322,7 +1610,7 @@ sqlcipher = ["sqlcipher3_binary"] name = "sqlglot" version = "21.1.1" description = "An easily customizable SQL parser and transpiler" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "sqlglot-21.1.1-py3-none-any.whl", hash = "sha256:228c11e4d185297e2c19fc6848db59a236375400ec44fabda1fc304e3e1ef97c"}, @@ -1344,6 +1632,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.12.3" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, + {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, +] + [[package]] name = "tqdm" version = "4.66.2" @@ -1409,20 +1708,19 @@ files = [ [[package]] name = "urllib3" -version = "2.2.0" +version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "zipp" @@ -1446,4 +1744,4 @@ parse = ["sqlglot"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "207eb763650d4c32f398696bc1cc0e9da370ca81c2ae8638c8d79ebeb87ec359" +content-hash = "443f16507226c597f8782edda4253191437c59723247f1c2a9c8f6d4bfefcbc2" diff --git a/pyproject.toml b/pyproject.toml index a39e70b..57f12c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sqlalchemy-declarative-extensions" -version = "0.7.0" +version = "0.7.1" authors = ["Dan Cardin "] description = "Library to declare additional kinds of objects not natively supported by SQLAlchemy/Alembic." @@ -47,14 +47,20 @@ psycopg = "*" alembic-utils = "0.8.1" black = ">=22.3.0" coverage = ">=5" -ruff = "0.1.15" mypy = "1.4.1" +pymysql = {version = "*", extras = ["rsa"]} pytest = ">=7" -pytest-xdist = "*" pytest-alembic = "*" pytest-mock-resources = { version = ">=2.6.13", extras = ["docker"] } +pytest-xdist = "*" +ruff = "0.1.15" sqlalchemy = {version = ">=1.4", extras = ["mypy"]} -pymysql = {version = "*", extras = ["rsa"]} + +# snowflake +fakesnow = {version = "~0.9.2", python = ">=3.9"} +snowflake-connector-python = {version = ">=3.7", python = ">=3.9"} +filelock = {version = "*", python = ">=3.9"} +numpy = {version = ">=1.26", python = ">=3.9"} [tool.poetry.extras] alembic = ["alembic"] @@ -91,6 +97,7 @@ filterwarnings = [ 'error', 'ignore:invalid escape sequence.*', 'ignore:distutils Version classes are deprecated. Use packaging.version instead.:DeprecationWarning', + 'ignore:_SixMetaPathImporter.find_spec.*:ImportWarning' ] pytester_example_dir = "tests/examples" markers = [ diff --git a/src/sqlalchemy_declarative_extensions/alembic/schema.py b/src/sqlalchemy_declarative_extensions/alembic/schema.py index b85f4c7..e93d515 100644 --- a/src/sqlalchemy_declarative_extensions/alembic/schema.py +++ b/src/sqlalchemy_declarative_extensions/alembic/schema.py @@ -1,7 +1,7 @@ +from alembic.autogenerate.api import AutogenContext from alembic.autogenerate.compare import comparators from alembic.autogenerate.render import renderers from alembic.operations import Operations -from sqlalchemy.schema import CreateSchema, DropSchema from sqlalchemy_declarative_extensions import schema from sqlalchemy_declarative_extensions.schema.base import Schemas @@ -15,30 +15,27 @@ @comparators.dispatch_for("schema") -def compare_schemas(autogen_context, upgrade_ops, schemas: Schemas): +def compare_schemas(autogen_context: AutogenContext, upgrade_ops, schemas: Schemas): + assert autogen_context.metadata schemas = autogen_context.metadata.info.get("schemas") if not schemas: return + assert autogen_context.connection result = schema.compare.compare_schemas(autogen_context.connection, schemas) upgrade_ops.ops[0:0] = result @renderers.dispatch_for(CreateSchemaOp) -def render_create_schema(_, op: CreateSchemaOp): - return f"op.create_schema('{op.schema.name}')" - - @renderers.dispatch_for(DropSchemaOp) -def render_drop_schema(_, op: DropSchemaOp): - return f"op.drop_schema('{op.schema.name}')" +def render_create_schema(autogen_context: AutogenContext, op: CreateSchemaOp): + statement = op.to_sql() + cls_name = statement.__class__.__name__ + autogen_context.imports.add(f"from sqlalchemy.sql.ddl import {cls_name}") + return f'op.execute({cls_name}("{statement.element}"))' @Operations.implementation_for(CreateSchemaOp) -def create_schema(operations, operation: CreateSchemaOp): - operations.execute(CreateSchema(operation.schema.name)) - - @Operations.implementation_for(DropSchemaOp) -def drop_schema(operations, operation: DropSchemaOp): - operations.execute(DropSchema(operation.schema.name)) +def create_schema(operations, operation: CreateSchemaOp): + operations.execute(operation.to_sql()) diff --git a/src/sqlalchemy_declarative_extensions/api.py b/src/sqlalchemy_declarative_extensions/api.py index 9cfe04e..d2a8727 100644 --- a/src/sqlalchemy_declarative_extensions/api.py +++ b/src/sqlalchemy_declarative_extensions/api.py @@ -171,12 +171,11 @@ def register_sqlalchemy_events( concrete_rows = metadata.info.get("rows") if concrete_schemas and schemas: - for schema in concrete_schemas: - event.listen( - metadata, - "before_create", - schema_ddl(schema), - ) + event.listen( + metadata, + "before_create", + schema_ddl, + ) if concrete_roles and roles: event.listen( diff --git a/src/sqlalchemy_declarative_extensions/dialects/postgresql/query.py b/src/sqlalchemy_declarative_extensions/dialects/postgresql/query.py index 13a7a2a..b26c3e5 100644 --- a/src/sqlalchemy_declarative_extensions/dialects/postgresql/query.py +++ b/src/sqlalchemy_declarative_extensions/dialects/postgresql/query.py @@ -42,7 +42,7 @@ def get_schemas_postgresql(connection: Connection): def check_schema_exists_postgresql(connection: Connection, name: str) -> bool: row = connection.execute(schema_exists_query, {"schema": name}).scalar() - return not bool(row) + return bool(row) def get_objects_postgresql(connection: Connection): diff --git a/src/sqlalchemy_declarative_extensions/dialects/query.py b/src/sqlalchemy_declarative_extensions/dialects/query.py index a1e44f5..4438a5f 100644 --- a/src/sqlalchemy_declarative_extensions/dialects/query.py +++ b/src/sqlalchemy_declarative_extensions/dialects/query.py @@ -20,20 +20,28 @@ get_view_postgresql, get_views_postgresql, ) +from sqlalchemy_declarative_extensions.dialects.snowflake.query import ( + check_schema_exists_snowflake, + get_schemas_snowflake, +) from sqlalchemy_declarative_extensions.dialects.sqlite.query import ( check_schema_exists_sqlite, + get_schemas_sqlite, get_views_sqlite, ) from sqlalchemy_declarative_extensions.sqlalchemy import dialect_dispatch, select get_schemas = dialect_dispatch( postgresql=get_schemas_postgresql, + sqlite=get_schemas_sqlite, + snowflake=get_schemas_snowflake, ) check_schema_exists = dialect_dispatch( postgresql=check_schema_exists_postgresql, sqlite=check_schema_exists_sqlite, mysql=check_schema_exists_mysql, + snowflake=check_schema_exists_snowflake, ) get_objects = dialect_dispatch( diff --git a/src/sqlalchemy_declarative_extensions/dialects/snowflake/__init__.py b/src/sqlalchemy_declarative_extensions/dialects/snowflake/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/sqlalchemy_declarative_extensions/dialects/snowflake/query.py b/src/sqlalchemy_declarative_extensions/dialects/snowflake/query.py new file mode 100644 index 0000000..b4ac41b --- /dev/null +++ b/src/sqlalchemy_declarative_extensions/dialects/snowflake/query.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from sqlalchemy.engine.base import Connection +from sqlalchemy.sql.expression import text + + +def get_schemas_snowflake(connection: Connection): + from sqlalchemy_declarative_extensions.schema.base import Schema + + schemas_query = text( + "SELECT schema_name" + " FROM information_schema.schemata" + " WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'main')" + ) + + return { + Schema(schema) for schema, *_ in connection.execute(schemas_query).fetchall() + } + + +def check_schema_exists_snowflake(connection: Connection, name: str) -> bool: + schema_exists_query = text( + "SELECT schema_name" + " FROM information_schema.schemata" + " WHERE schema_name = :schema" + ) + row = connection.execute(schema_exists_query, {"schema": name}).scalar() + return bool(row) diff --git a/src/sqlalchemy_declarative_extensions/dialects/sqlite/query.py b/src/sqlalchemy_declarative_extensions/dialects/sqlite/query.py index 80a6f8b..4df14ca 100644 --- a/src/sqlalchemy_declarative_extensions/dialects/sqlite/query.py +++ b/src/sqlalchemy_declarative_extensions/dialects/sqlite/query.py @@ -5,6 +5,13 @@ from sqlalchemy_declarative_extensions.view.base import View +def get_schemas_sqlite(connection: Connection): + from sqlalchemy_declarative_extensions.schema.base import Schema + + schemas = connection.execute(text("PRAGMA database_list")).fetchall() + return {Schema(schema) for _, schema, *_ in schemas if schema not in {"main"}} + + def check_schema_exists_sqlite(connection: Connection, name: str) -> bool: """Check whether the given schema exists. diff --git a/src/sqlalchemy_declarative_extensions/schema/__init__.py b/src/sqlalchemy_declarative_extensions/schema/__init__.py index 30a039d..62acf17 100644 --- a/src/sqlalchemy_declarative_extensions/schema/__init__.py +++ b/src/sqlalchemy_declarative_extensions/schema/__init__.py @@ -1,6 +1,8 @@ +from sqlalchemy_declarative_extensions.schema import compare from sqlalchemy_declarative_extensions.schema.base import Schema, Schemas __all__ = [ "Schema", "Schemas", + "compare", ] diff --git a/src/sqlalchemy_declarative_extensions/schema/compare.py b/src/sqlalchemy_declarative_extensions/schema/compare.py index 99b4ed9..134c454 100644 --- a/src/sqlalchemy_declarative_extensions/schema/compare.py +++ b/src/sqlalchemy_declarative_extensions/schema/compare.py @@ -4,6 +4,7 @@ from typing import Union from sqlalchemy.engine.base import Connection +from sqlalchemy.sql.ddl import CreateSchema, DropSchema from sqlalchemy_declarative_extensions.dialects import get_schemas from sqlalchemy_declarative_extensions.schema.base import Schema, Schemas @@ -21,6 +22,9 @@ def create_schema(cls, operations, schema, **kwargs): def reverse(self): return DropSchemaOp(self.schema) + def to_sql(self): + return CreateSchema(self.schema.name) + @dataclass class DropSchemaOp: @@ -34,6 +38,9 @@ def drop_schema(cls, operations, schema, **kwargs): def reverse(self): return CreateSchemaOp(self.schema) + def to_sql(self): + return DropSchema(self.schema.name) + SchemaOp = Union[CreateSchemaOp, DropSchemaOp] diff --git a/src/sqlalchemy_declarative_extensions/schema/ddl.py b/src/sqlalchemy_declarative_extensions/schema/ddl.py index 8671e15..5cb2555 100644 --- a/src/sqlalchemy_declarative_extensions/schema/ddl.py +++ b/src/sqlalchemy_declarative_extensions/schema/ddl.py @@ -1,14 +1,18 @@ -from sqlalchemy.schema import CreateSchema +from __future__ import annotations -from sqlalchemy_declarative_extensions.dialects import check_schema_exists -from sqlalchemy_declarative_extensions.schema import Schema +from sqlalchemy import MetaData +from sqlalchemy.engine import Connection +from sqlalchemy_declarative_extensions.schema import Schemas +from sqlalchemy_declarative_extensions.schema.compare import compare_schemas -def schema_ddl(schema: Schema): - ddl = CreateSchema(schema.name) - return ddl.execute_if(callable_=check_schema) +def schema_ddl(metadata: MetaData, connection: Connection, **_): + roles: Schemas | None = metadata.info.get("schemas") + if not roles: # pragma: no cover + return -def check_schema(ddl, target, connection, **_): - schema = ddl.element - return check_schema_exists(connection, name=schema) + result = compare_schemas(connection, roles) + for op in result: + statements = op.to_sql() + connection.execute(statements) diff --git a/src/sqlalchemy_declarative_extensions/sqlalchemy.py b/src/sqlalchemy_declarative_extensions/sqlalchemy.py index b624759..24b3740 100644 --- a/src/sqlalchemy_declarative_extensions/sqlalchemy.py +++ b/src/sqlalchemy_declarative_extensions/sqlalchemy.py @@ -11,11 +11,12 @@ class HasMetaData(Protocol): metadata: MetaData -def dialect_dispatch(postgresql=None, sqlite=None, mysql=None): +def dialect_dispatch(postgresql=None, sqlite=None, mysql=None, snowflake=None): dispatchers = { "postgresql": postgresql, "sqlite": sqlite, "mysql": mysql, + "snowflake": snowflake, } def dispatch(connection: Connection, *args, **kwargs): diff --git a/tests/conftest.py b/tests/conftest.py index 7bedb20..2e2f639 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,6 +44,23 @@ def pmr_mysql_config(): return MysqlConfig(image="mysql:8", port=None, ci_port=None) +@pytest.fixture +def snowflake(): + try: + import snowflake.sqlalchemy + except ImportError: + pytest.skip("Snowflake not installed") + + import fakesnow + from sqlalchemy.engine.create import create_engine + + with fakesnow.patch( + create_database_on_connect=True, + create_schema_on_connect=False, + ): + yield create_engine("snowflake://test/test/information_schema") + + @pytest.fixture(autouse=True) def clear_registry(): """Clear out state accumulated by importing alembic modules in env.pys. diff --git a/tests/schema/test_drop.py b/tests/schema/test_drop.py new file mode 100644 index 0000000..5ad80db --- /dev/null +++ b/tests/schema/test_drop.py @@ -0,0 +1,45 @@ +from pytest_mock_resources import create_postgres_fixture, create_sqlite_fixture +from sqlalchemy.sql.ddl import CreateSchema + +from sqlalchemy_declarative_extensions import ( + Schemas, + declarative_database, + register_sqlalchemy_events, +) +from sqlalchemy_declarative_extensions.dialects import check_schema_exists +from sqlalchemy_declarative_extensions.sqlalchemy import declarative_base + +_Base = declarative_base() + + +@declarative_database +class Base(_Base): # type: ignore + __abstract__ = True + + schemas = Schemas() + + +register_sqlalchemy_events(Base.metadata, schemas=True) + +pg = create_postgres_fixture(scope="function", engine_kwargs={"echo": True}) +sqlite = create_sqlite_fixture() + + +def test_createall_schema_pg(pg): + with pg.begin() as conn: + conn.execute(CreateSchema("foo")) + + Base.metadata.create_all(bind=pg) + + with pg.connect() as conn: + assert check_schema_exists(conn, "foo") is False + + +def test_createall_schema_snowflake(snowflake): + with snowflake.begin() as conn: + conn.execute(CreateSchema("foo")) + + Base.metadata.create_all(bind=snowflake) + + with snowflake.connect() as conn: + assert check_schema_exists(conn, "foo") is False diff --git a/tests/schema/test_sqlalchemy.py b/tests/schema/test_sqlalchemy.py index 3846a98..f5ec57b 100644 --- a/tests/schema/test_sqlalchemy.py +++ b/tests/schema/test_sqlalchemy.py @@ -8,9 +8,6 @@ ) from sqlalchemy_declarative_extensions.sqlalchemy import declarative_base -() - - _Base = declarative_base() @@ -25,7 +22,9 @@ class Foo(Base): __tablename__ = "foo" __table_args__ = {"schema": "fooschema"} - id = sqlalchemy.Column(sqlalchemy.types.Integer(), primary_key=True) + id = sqlalchemy.Column( + sqlalchemy.types.Integer(), primary_key=True, autoincrement=False + ) register_sqlalchemy_events(Base.metadata, schemas=True) @@ -46,3 +45,10 @@ def test_createall_schema_sqlite(sqlite): with sqlite.connect() as conn: result = conn.execute(Foo.__table__.select()).fetchall() assert result == [] + + +def test_createall_schema_snowflake(snowflake): + Base.metadata.create_all(bind=snowflake, checkfirst=False) + with snowflake.connect() as conn: + result = conn.execute(Foo.__table__.select()).fetchall() + assert result == [] diff --git a/tests/view/test_sqlalchemy.py b/tests/view/test_sqlalchemy.py index 1414aed..1423c05 100644 --- a/tests/view/test_sqlalchemy.py +++ b/tests/view/test_sqlalchemy.py @@ -8,7 +8,6 @@ from sqlalchemy_declarative_extensions import ( Row, Rows, - Schemas, declarative_database, register_sqlalchemy_events, view, @@ -16,9 +15,6 @@ from sqlalchemy_declarative_extensions.sqlalchemy import declarative_base, select from tests import skip_sqlalchemy13 -() - - _Base = declarative_base() @@ -26,18 +22,16 @@ class Base(_Base): # type: ignore __abstract__ = True - schemas = Schemas().are("fooschema") rows = Rows().are( - Row("fooschema.foo", id=1), - Row("fooschema.foo", id=2), - Row("fooschema.foo", id=12), - Row("fooschema.foo", id=13), + Row("foo", id=1), + Row("foo", id=2), + Row("foo", id=12), + Row("foo", id=13), ) class Foo(Base): __tablename__ = "foo" - __table_args__ = {"schema": "fooschema"} id = Column(types.Integer(), primary_key=True) @@ -48,7 +42,6 @@ class Foo(Base): @view(Base, register_as_model=True) class Bar: __tablename__ = "bar" - __table_args__ = {"schema": "fooschema"} __view__ = select(foo_table.c.id).where(foo_table.c.id > 10) id = Column(types.Integer(), primary_key=True) @@ -57,7 +50,6 @@ class Bar: @view(Base, register_as_model=True) class Baz: __tablename__ = "baz" - __table_args__ = {"schema": "fooschema"} __view__ = select(foo_table.c.id).where(foo_table.c.id < 10) id = Column(types.Integer(), primary_key=True)