diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b2c6441..bc120b8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -26,7 +26,7 @@ jobs: with: python_version: "3.11" pre_commit_version: "4.0.1" - directory: "." + directory: "src" - name: 'Pyright type checks' uses: ./.github/workflows/templates/pyright_type_checks with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 39ec55f..6d5412c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,7 +26,6 @@ jobs: with: python_version: '3.11' uv_version: '0.4.27' - path_to_packages: './packages' - name: Upload wheels uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/templates/release/build/action.yaml b/.github/workflows/templates/release/build/action.yaml index ad6bd61..ef4d32e 100644 --- a/.github/workflows/templates/release/build/action.yaml +++ b/.github/workflows/templates/release/build/action.yaml @@ -10,10 +10,6 @@ inputs: description: 'uv version to use' required: true default: 'latest' - path_to_packages: - description: 'Path to python packages' - required: true - default: 'packages' runs: using: 'composite' @@ -40,10 +36,10 @@ runs: uses: ./.github/workflows/templates/release/utils/bump_version with: version: ${{ steps.version.outputs.version }} - root: ${{ inputs.path_to_packages }} - glob: '**/src/**/version.py,**/pyproject.toml' + root: '.' + glob: 'src/**/version.py,**/pyproject.toml' - name: 'Build python packages' id: build run: | - uv build --all + uv build shell: bash diff --git a/.justfile b/.justfile index b8b3358..cdbcac4 100644 --- a/.justfile +++ b/.justfile @@ -6,7 +6,7 @@ alias d := docs # Install python dependencies install: - uv sync + uv sync --all-extras # Install pre-commit hooks pre_commit_setup: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f7b9e7..41c8db5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: rev: "1.7.0" hooks: - id: interrogate - args: [-vv, --fail-under=0, ./packages] + args: [-vv, --fail-under=0, ./src] - repo: https://github.com/ambv/black rev: 24.1.1 diff --git a/docs/snippets/io_manager_pandas.py b/docs/snippets/io_manager_pandas.py index 7b400bd..5307607 100644 --- a/docs/snippets/io_manager_pandas.py +++ b/docs/snippets/io_manager_pandas.py @@ -1,8 +1,9 @@ import pandas as pd from dagster import Definitions, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" CATALOG_WAREHOUSE = ( "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" diff --git a/docs/snippets/io_manager_polars.py b/docs/snippets/io_manager_polars.py index d4a13ee..c3eae3d 100644 --- a/docs/snippets/io_manager_polars.py +++ b/docs/snippets/io_manager_polars.py @@ -1,9 +1,10 @@ import pandas as pd import polars as pl from dagster import Definitions, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_polars import IcebergPolarsIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" CATALOG_WAREHOUSE = ( "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" diff --git a/docs/snippets/io_manager_pyarrow.py b/docs/snippets/io_manager_pyarrow.py index 3fb82b7..e143bfd 100644 --- a/docs/snippets/io_manager_pyarrow.py +++ b/docs/snippets/io_manager_pyarrow.py @@ -1,6 +1,7 @@ import pandas as pd import pyarrow as pa from dagster import Definitions, asset + from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" diff --git a/docs/snippets/multiple_io_managers.py b/docs/snippets/multiple_io_managers.py index 8f249d3..1408c76 100644 --- a/docs/snippets/multiple_io_managers.py +++ b/docs/snippets/multiple_io_managers.py @@ -1,8 +1,9 @@ import pandas as pd from dagster import Definitions, FilesystemIOManager, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/catalog.db" CATALOG_WAREHOUSE = "file:///home/vscode/workspace/.tmp/examples/warehouse" FS_BASE_DIR = "/home/vscode/workspace/.tmp/examples/images" diff --git a/docs/snippets/multiple_schemas.py b/docs/snippets/multiple_schemas.py index c570514..12f5680 100644 --- a/docs/snippets/multiple_schemas.py +++ b/docs/snippets/multiple_schemas.py @@ -1,8 +1,9 @@ import pandas as pd from dagster import Definitions, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/catalog.db" CATALOG_WAREHOUSE = "file:///home/vscode/workspace/.tmp/examples/warehouse" diff --git a/docs/snippets/partitions_multiple.py b/docs/snippets/partitions_multiple.py index 305e1ec..e48824f 100644 --- a/docs/snippets/partitions_multiple.py +++ b/docs/snippets/partitions_multiple.py @@ -9,9 +9,10 @@ StaticPartitionDefinition, asset, ) -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/catalog.db" CATALOG_WAREHOUSE = "file:///home/vscode/workspace/.tmp/examples/warehouse" diff --git a/docs/snippets/partitions_static.py b/docs/snippets/partitions_static.py index 0de0dbb..5e479c7 100644 --- a/docs/snippets/partitions_static.py +++ b/docs/snippets/partitions_static.py @@ -1,8 +1,9 @@ import pandas as pd from dagster import Definitions, StaticPartitionsDefinition, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/catalog.db" CATALOG_WAREHOUSE = "file:///home/vscode/workspace/.tmp/examples/warehouse" diff --git a/docs/snippets/partitions_time.py b/docs/snippets/partitions_time.py index 928f6f6..ef0fd46 100644 --- a/docs/snippets/partitions_time.py +++ b/docs/snippets/partitions_time.py @@ -3,9 +3,10 @@ import pandas as pd from dagster import DailyPartitionsDefinition, Definitions, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/catalog.db" CATALOG_WAREHOUSE = "file:///home/vscode/workspace/.tmp/examples/warehouse" diff --git a/docs/snippets/pyiceberg_resource.py b/docs/snippets/pyiceberg_resource.py index 2fb1eb1..5bd8766 100644 --- a/docs/snippets/pyiceberg_resource.py +++ b/docs/snippets/pyiceberg_resource.py @@ -4,6 +4,7 @@ import pandas as pd from dagster import Definitions, asset + from dagster_pyiceberg import IcebergSqlCatalogConfig, IcebergTableResource CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/catalog.db" diff --git a/docs/snippets/select_columns.py b/docs/snippets/select_columns.py index b64baf6..018eb67 100644 --- a/docs/snippets/select_columns.py +++ b/docs/snippets/select_columns.py @@ -1,8 +1,9 @@ import pandas as pd from dagster import AssetIn, Definitions, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" CATALOG_WAREHOUSE = ( "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" diff --git a/docs/snippets/table_properties.py b/docs/snippets/table_properties.py index cd9ebad..32d8594 100644 --- a/docs/snippets/table_properties.py +++ b/docs/snippets/table_properties.py @@ -1,8 +1,9 @@ import pandas as pd from dagster import Definitions, asset -from dagster_pyiceberg import IcebergSqlCatalogConfig from dagster_pyiceberg_pandas import IcebergPandasIOManager +from dagster_pyiceberg import IcebergSqlCatalogConfig + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" CATALOG_WAREHOUSE = ( "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" diff --git a/packages/dagster_pyiceberg/LICENSE.txt b/packages/dagster_pyiceberg/LICENSE.txt deleted file mode 100644 index 0478abd..0000000 --- a/packages/dagster_pyiceberg/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2024 Jasper Ginn. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/dagster_pyiceberg/README.md b/packages/dagster_pyiceberg/README.md deleted file mode 100644 index 1c80fb5..0000000 --- a/packages/dagster_pyiceberg/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# dagster-pyiceberg - -Dagster IO manager for managing [PyIceberg](https://github.com/apache/iceberg-python) tables. - -Documentation can be found [here](https://jasperhg90.github.io/dagster-pyiceberg/). diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/__init__.py b/packages/dagster_pyiceberg/src/dagster_pyiceberg/__init__.py deleted file mode 100644 index 03c64d4..0000000 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/__init__.py +++ /dev/null @@ -1,92 +0,0 @@ -from typing import Sequence - -# from dagster._core.libraries import DagsterLibraryRegistry -from dagster._core.storage.db_io_manager import DbTypeHandler -from dagster_pyiceberg.config import ( - IcebergRestCatalogConfig as IcebergRestCatalogConfig, -) -from dagster_pyiceberg.config import IcebergSqlCatalogConfig as IcebergSqlCatalogConfig -from dagster_pyiceberg.handler import ( - IcebergPyArrowTypeHandler as IcebergPyArrowTypeHandler, -) -from dagster_pyiceberg.io_manager import IcebergIOManager as IcebergIOManager -from dagster_pyiceberg.resource import IcebergTableResource as IcebergTableResource -from dagster_pyiceberg.version import __version__ as __version__ - - -class IcebergPyarrowIOManager(IcebergIOManager): - """An IO manager definition that reads inputs from and writes outputs to Iceberg tables using PyArrow. - - Examples: - .. code-block:: python - - import pandas as pd - import pyarrow as pa - from dagster import Definitions, asset - from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig - - CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" - CATALOG_WAREHOUSE = ( - "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" - ) - - resources = { - "io_manager": IcebergPyarrowIOManager( - name="test", - config=IcebergSqlCatalogConfig( - properties={"uri": CATALOG_URI, "warehouse": CATALOG_WAREHOUSE} - ), - schema="dagster", - ) - } - - @asset - def iris_dataset() -> pd.DataFrame: - pa.Table.from_pandas( - pd.read_csv( - "https://docs.dagster.io/assets/iris.csv", - names=[ - "sepal_length_cm", - "sepal_width_cm", - "petal_length_cm", - "petal_width_cm", - "species", - ], - ) - ) - - defs = Definitions(assets=[iris_dataset], resources=resources) - - If you do not provide a schema, Dagster will determine a schema based on the assets and ops using - the I/O Manager. For assets, the schema will be determined from the asset key, as in the above example. - For ops, the schema can be specified by including a "schema" entry in output metadata. If none - of these is provided, the schema will default to "public". - - .. code-block:: python - - @op( - out={"my_table": Out(metadata={"schema": "my_schema"})} - ) - def make_my_table() -> pd.DataFrame: - ... - - To only use specific columns of a table as input to a downstream op or asset, add the metadata "columns" to the - In or AssetIn. - - .. code-block:: python - - @asset( - ins={"my_table": AssetIn("my_table", metadata={"columns": ["a"]})} - ) - def my_table_a(my_table: pd.DataFrame): - # my_table will just contain the data from column "a" - ... - - """ - - @staticmethod - def type_handlers() -> Sequence[DbTypeHandler]: - return [IcebergPyArrowTypeHandler()] - - -# DagsterLibraryRegistry.register("dagster-pyiceberg", __version__) diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/__init__.py b/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/__init__.py deleted file mode 100644 index c3dacdb..0000000 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from dagster_pyiceberg._utils.io import CatalogTypes as CatalogTypes -from dagster_pyiceberg._utils.io import table_reader as table_reader -from dagster_pyiceberg._utils.io import table_writer as table_writer diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/handler.py b/packages/dagster_pyiceberg/src/dagster_pyiceberg/handler.py deleted file mode 100644 index 529f09d..0000000 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/handler.py +++ /dev/null @@ -1,123 +0,0 @@ -from abc import abstractmethod -from typing import Generic, Sequence, Type, TypeVar, Union, cast - -import pyarrow as pa -from dagster import InputContext, MetadataValue, OutputContext, TableColumn, TableSchema -from dagster._core.storage.db_io_manager import DbTypeHandler, TableSlice -from dagster_pyiceberg._utils import CatalogTypes, table_reader, table_writer -from pyiceberg.table import DataScan -from pyiceberg.table.snapshots import Snapshot - -U = TypeVar("U") - -ArrowTypes = Union[pa.Table, pa.RecordBatchReader] - - -class IcebergBaseArrowTypeHandler(DbTypeHandler[U], Generic[U]): - """ - Base class for PyIceberg type handlers - - To implement a new type handler (e.g. pandas), subclass this class and implement - the `from_arrow` and `to_arrow` methods. These methods convert between the target - type (e.g. pandas DataFrame) and pyarrow Table. You must also declare a property - `supported_types` that lists the types that the handler supports. - See `IcebergPyArrowTypeHandler` for an example. - - Target types are determined in the user code by type annotating the output of - a dagster asset. - """ - - @abstractmethod - def from_arrow(self, obj: DataScan, target_type: type) -> U: ... - - # TODO: deltalake uses record batch reader, as `write_deltalake` takes this as - # an input, see - # but this is not supported by pyiceberg I think. We need to check this. - @abstractmethod - def to_arrow(self, obj: U) -> pa.Table: ... - - def handle_output( - self, - context: OutputContext, - table_slice: TableSlice, - obj: U, - connection: CatalogTypes, - ): - """Stores pyarrow types in Iceberg table""" - metadata = context.definition_metadata or {} # noqa - resource_config = context.resource_config or {} - - # NB: not checking properties here, except for protected properties - table_properties_usr = metadata.get("table_properties", {}) - for k, _ in table_properties_usr.items(): - if k in ["created_by", "run_id"]: - raise KeyError( - f"Table properties cannot contain the following keys: {k}" - ) - - partition_spec_update_mode = cast( - str, resource_config["partition_spec_update_mode"] - ) - schema_update_mode = cast(str, resource_config["schema_update_mode"]) - - data = self.to_arrow(obj) - - table_writer( - table_slice=table_slice, - data=data, - catalog=connection, - partition_spec_update_mode=partition_spec_update_mode, - schema_update_mode=schema_update_mode, - dagster_run_id=context.run_id, - dagster_partition_key=( - context.partition_key if context.has_asset_partitions else None - ), - table_properties=table_properties_usr, - ) - - table_ = connection.load_table(f"{table_slice.schema}.{table_slice.table}") - - current_snapshot = cast(Snapshot, table_.current_snapshot()) - - context.add_output_metadata( - { - "table_columns": MetadataValue.table_schema( - TableSchema( - columns=[ - TableColumn(name=f["name"], type=str(f["type"])) - for f in table_.schema().model_dump()["fields"] - ] - ) - ), - **current_snapshot.model_dump(), - } - ) - - def load_input( - self, - context: InputContext, - table_slice: TableSlice, - connection: CatalogTypes, - ) -> U: - """Loads the input as a pyarrow Table""" - return self.from_arrow( - table_reader(table_slice=table_slice, catalog=connection), - context.dagster_type.typing_type, - ) - - -class IcebergPyArrowTypeHandler(IcebergBaseArrowTypeHandler[ArrowTypes]): - """Type handler that converts data between Iceberg tables and pyarrow Tables""" - - def from_arrow(self, obj: DataScan, target_type: Type[ArrowTypes]) -> ArrowTypes: - if target_type == pa.Table: - return obj.to_arrow() - else: - return obj.to_arrow_batch_reader() - - def to_arrow(self, obj: ArrowTypes) -> pa.Table: - return obj - - @property - def supported_types(self) -> Sequence[Type[object]]: - return (pa.Table, pa.RecordBatchReader) diff --git a/packages/dagster_pyiceberg_pandas/LICENSE.txt b/packages/dagster_pyiceberg_pandas/LICENSE.txt deleted file mode 100644 index 0478abd..0000000 --- a/packages/dagster_pyiceberg_pandas/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2024 Jasper Ginn. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/dagster_pyiceberg_pandas/README.md b/packages/dagster_pyiceberg_pandas/README.md deleted file mode 100644 index eb4a430..0000000 --- a/packages/dagster_pyiceberg_pandas/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# dagster-pyiceberg-pandas - -Dagster IO manager for managing [PyIceberg](https://github.com/apache/iceberg-python) tables using Pandas. - -Documentation can be found [here](https://jasperhg90.github.io/dagster-pyiceberg/). diff --git a/packages/dagster_pyiceberg_pandas/pyproject.toml b/packages/dagster_pyiceberg_pandas/pyproject.toml deleted file mode 100644 index c7d5f72..0000000 --- a/packages/dagster_pyiceberg_pandas/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[project] -name = "dagster-pyiceberg-pandas" -version = "0.0.0" -description = "Package for storing Pandas DataFrames in PyIceberg tables." -authors = [ - {name="Jasper Ginn", email="jasperginn@gmail.com"} -] -requires-python = ">=3.10" -readme = "README.md" -dependencies = [ - "dagster-pyiceberg", - "pandas>=2.2.3", -] -maintainers = [ - {name="Jasper Ginn", email="jasperginn@gmail.com"} -] -license = {file = "LICENSE.txt"} -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12" -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project.urls] -Homepage = "https://jasperhg90.github.io/dagster-pyiceberg/" -Documentation = "https://jasperhg90.github.io/dagster-pyiceberg/" -Repository = "https://github.com/JasperHG90/dagster-pyiceberg" -"Bug Tracker" = "https://github.com/JasperHG90/dagster-pyiceberg/issues" diff --git a/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/__init__.py b/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/__init__.py deleted file mode 100644 index de27edb..0000000 --- a/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# from dagster._core.libraries import DagsterLibraryRegistry -from dagster_pyiceberg_pandas.pyiceberg_pandas_type_handler import ( - IcebergPandasIOManager as IcebergPandasIOManager, -) -from dagster_pyiceberg_pandas.pyiceberg_pandas_type_handler import ( - IcebergPandasTypeHandler as IcebergPandasTypeHandler, -) -from dagster_pyiceberg_pandas.version import __version__ as __version__ - -# DagsterLibraryRegistry.register("dagster-pyiceberg-pandas", __version__) diff --git a/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/py.typed b/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/pyiceberg_pandas_type_handler.py b/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/pyiceberg_pandas_type_handler.py deleted file mode 100644 index 97f3d75..0000000 --- a/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/pyiceberg_pandas_type_handler.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Optional, Sequence, Type - -import pandas as pd -import pyarrow as pa -from dagster._core.storage.db_io_manager import DbTypeHandler -from dagster_pyiceberg import IcebergIOManager -from dagster_pyiceberg.handler import ( - IcebergBaseArrowTypeHandler, - IcebergPyArrowTypeHandler, -) -from pyiceberg import table - - -class IcebergPandasTypeHandler(IcebergBaseArrowTypeHandler[pd.DataFrame]): - def from_arrow( - self, obj: table.DataScan, target_type: Type[pd.DataFrame] - ) -> pd.DataFrame: - return obj.to_pandas() - - def to_arrow(self, obj: pd.DataFrame) -> pa.Table: - return pa.Table.from_pandas(obj) - - @property - def supported_types(self) -> Sequence[Type[object]]: - return [pd.DataFrame] - - -class IcebergPandasIOManager(IcebergIOManager): - """An IO manager definition that reads inputs from and writes outputs to Iceberg tables using Pandas. - - Examples: - .. code-block:: python - - import pandas as pd - from dagster import Definitions, asset - from dagster_pyiceberg import IcebergSqlCatalogConfig - from dagster_pyiceberg_pandas import IcebergPandasIOManager - - CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" - CATALOG_WAREHOUSE = ( - "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" - ) - - resources = { - "io_manager": IcebergPandasIOManager( - name="test", - config=IcebergSqlCatalogConfig( - properties={"uri": CATALOG_URI, "warehouse": CATALOG_WAREHOUSE} - ), - schema="dagster", - ) - } - - @asset - def iris_dataset() -> pd.DataFrame: - return pd.read_csv( - "https://docs.dagster.io/assets/iris.csv", - names=[ - "sepal_length_cm", - "sepal_width_cm", - "petal_length_cm", - "petal_width_cm", - "species", - ], - ) - - defs = Definitions(assets=[iris_dataset], resources=resources) - - If you do not provide a schema, Dagster will determine a schema based on the assets and ops using - the I/O Manager. For assets, the schema will be determined from the asset key, as in the above example. - For ops, the schema can be specified by including a "schema" entry in output metadata. If none - of these is provided, the schema will default to "public". - - .. code-block:: python - - @op( - out={"my_table": Out(metadata={"schema": "my_schema"})} - ) - def make_my_table() -> pd.DataFrame: - ... - - To only use specific columns of a table as input to a downstream op or asset, add the metadata "columns" to the - In or AssetIn. - - .. code-block:: python - - @asset( - ins={"my_table": AssetIn("my_table", metadata={"columns": ["a"]})} - ) - def my_table_a(my_table: pd.DataFrame): - # my_table will just contain the data from column "a" - ... - - """ - - @staticmethod - def type_handlers() -> Sequence[DbTypeHandler]: - return [IcebergPandasTypeHandler(), IcebergPyArrowTypeHandler()] - - @staticmethod - def default_load_type() -> Optional[Type]: - return pd.DataFrame diff --git a/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/version.py b/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/version.py deleted file mode 100644 index 6c8e6b9..0000000 --- a/packages/dagster_pyiceberg_pandas/src/dagster_pyiceberg_pandas/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.0.0" diff --git a/packages/dagster_pyiceberg_polars/LICENSE.txt b/packages/dagster_pyiceberg_polars/LICENSE.txt deleted file mode 100644 index 0478abd..0000000 --- a/packages/dagster_pyiceberg_polars/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2024 Jasper Ginn. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/dagster_pyiceberg_polars/README.md b/packages/dagster_pyiceberg_polars/README.md deleted file mode 100644 index c4d6ff9..0000000 --- a/packages/dagster_pyiceberg_polars/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# dagster-pyiceberg-polars - -Dagster IO manager for managing [PyIceberg](https://github.com/apache/iceberg-python) tables using Polars. - -Documentation can be found [here](https://jasperhg90.github.io/dagster-pyiceberg/). diff --git a/packages/dagster_pyiceberg_polars/pyproject.toml b/packages/dagster_pyiceberg_polars/pyproject.toml deleted file mode 100644 index 279d0a2..0000000 --- a/packages/dagster_pyiceberg_polars/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[project] -name = "dagster-pyiceberg-polars" -version = "0.0.0" -description = "Package for storing Polars DataFrames in PyIceberg tables." -authors = [ - {name="Jasper Ginn", email="jasperginn@gmail.com"} -] -requires-python = ">=3.10" -readme = "README.md" -dependencies = [ - "dagster-pyiceberg", - "polars>=1.11.0", -] -maintainers = [ - {name="Jasper Ginn", email="jasperginn@gmail.com"} -] -license = {file = "LICENSE.txt"} -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12" -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project.urls] -Homepage = "https://jasperhg90.github.io/dagster-pyiceberg/" -Documentation = "https://jasperhg90.github.io/dagster-pyiceberg/" -Repository = "https://github.com/JasperHG90/dagster-pyiceberg" -"Bug Tracker" = "https://github.com/JasperHG90/dagster-pyiceberg/issues" diff --git a/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/__init__.py b/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/__init__.py deleted file mode 100644 index 73dfa09..0000000 --- a/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# from dagster._core.libraries import DagsterLibraryRegistry -from dagster_pyiceberg_polars.pyiceberg_polars_type_handler import ( - IcebergPolarsIOManager as IcebergPolarsIOManager, -) -from dagster_pyiceberg_polars.pyiceberg_polars_type_handler import ( - IcebergPolarsTypeHandler as IcebergPolarsTypeHandler, -) -from dagster_pyiceberg_polars.version import __version__ as __version__ - -# DagsterLibraryRegistry.register("dagster-pyiceberg-polars", __version__) diff --git a/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/py.typed b/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/pyiceberg_polars_type_handler.py b/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/pyiceberg_polars_type_handler.py deleted file mode 100644 index 2093528..0000000 --- a/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/pyiceberg_polars_type_handler.py +++ /dev/null @@ -1,115 +0,0 @@ -from typing import Optional, Sequence, Type, Union - -import polars as pl -import pyarrow as pa -from dagster._core.storage.db_io_manager import DbTypeHandler -from dagster_pyiceberg import IcebergIOManager -from dagster_pyiceberg.handler import ( - IcebergBaseArrowTypeHandler, - IcebergPyArrowTypeHandler, -) -from pyiceberg import table - -PolarsTypes = Union[pl.DataFrame, pl.LazyFrame] - - -class IcebergPolarsTypeHandler(IcebergBaseArrowTypeHandler[PolarsTypes]): - def from_arrow( - self, obj: table.DataScan, target_type: Type[PolarsTypes] - ) -> PolarsTypes: - return ( # type: ignore - pl.from_arrow(obj.to_arrow()) - if target_type == pl.DataFrame - else pl.from_arrow(obj.to_arrow_batch_reader()) - ) - - def to_arrow(self, obj: PolarsTypes) -> pa.Table: - return ( - obj.collect().to_arrow() - if isinstance(obj, pl.LazyFrame) - else obj.to_arrow() - ) - - @property - def supported_types(self) -> Sequence[Type[object]]: - return [pl.DataFrame, pl.LazyFrame] - - -class IcebergPolarsIOManager(IcebergIOManager): - """An IO manager definition that reads inputs from and writes outputs to Iceberg tables using PyArrow. - - Examples: - .. code-block:: python - - import pandas as pd - import polars as pl - from dagster import Definitions, asset - from dagster_pyiceberg import IcebergSqlCatalogConfig - from dagster_pyiceberg_polars import IcebergPolarsIOManager - - CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" - CATALOG_WAREHOUSE = ( - "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" - ) - - resources = { - "io_manager": IcebergPolarsIOManager( - name="test", - config=IcebergSqlCatalogConfig( - properties={"uri": CATALOG_URI, "warehouse": CATALOG_WAREHOUSE} - ), - schema="dagster", - ) - } - - @asset - def iris_dataset() -> pl.DataFrame: - return pl.from_pandas( - pd.read_csv( - "https://docs.dagster.io/assets/iris.csv", - names=[ - "sepal_length_cm", - "sepal_width_cm", - "petal_length_cm", - "petal_width_cm", - "species", - ], - ) - ) - - defs = Definitions(assets=[iris_dataset], resources=resources) - - If you do not provide a schema, Dagster will determine a schema based on the assets and ops using - the I/O Manager. For assets, the schema will be determined from the asset key, as in the above example. - For ops, the schema can be specified by including a "schema" entry in output metadata. If none - of these is provided, the schema will default to "public". - - .. code-block:: python - - @op( - out={"my_table": Out(metadata={"schema": "my_schema"})} - ) - def make_my_table() -> pd.DataFrame: - ... - - To only use specific columns of a table as input to a downstream op or asset, add the metadata "columns" to the - In or AssetIn. - - .. code-block:: python - - @asset( - ins={"my_table": AssetIn("my_table", metadata={"columns": ["a"]})} - ) - def my_table_a(my_table: pd.DataFrame): - # my_table will just contain the data from column "a" - ... - - """ - - @staticmethod - def type_handlers() -> Sequence[DbTypeHandler]: - return [IcebergPolarsTypeHandler(), IcebergPyArrowTypeHandler()] - - @staticmethod - def default_load_type() -> Optional[Type]: - return pl.DataFrame diff --git a/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/version.py b/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/version.py deleted file mode 100644 index 6c8e6b9..0000000 --- a/packages/dagster_pyiceberg_polars/src/dagster_pyiceberg_polars/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.0.0" diff --git a/pyproject.toml b/pyproject.toml index ca6bae7..18afc6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,27 +1,51 @@ [project] -name = "dagster-pyiceberg-project" +name = "dagster-pyiceberg" version = "0.0.0" -description = "Libraries that provide Dagster IO managers for PyIceberg." +description = "Package for PyIceberg-specific Dagster framework op and resource components." authors = [ {name="Jasper Ginn", email="jasperginn@gmail.com"} ] requires-python = ">=3.10" readme = "README.md" -dependencies = [] +dependencies = [ + "pyiceberg[pyarrow]==0.8.0rc1", + "dagster>=1.8.2", + "pendulum>=3.0.0", + "tenacity>=8.5.0", +] +maintainers = [ + {name="Jasper Ginn", email="jasperginn@gmail.com"} +] +license = {file = "LICENSE.txt"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12" +] -[tool.uv] -default-groups = ["dev", "docs"] -package = false +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" -[tool.uv.workspace] -members = [ - "packages/*" -] +[project.urls] +Homepage = "https://jasperhg90.github.io/dagster-pyiceberg/" +Documentation = "https://jasperhg90.github.io/dagster-pyiceberg/" +Repository = "https://github.com/JasperHG90/dagster-pyiceberg" +"Bug Tracker" = "https://github.com/JasperHG90/dagster-pyiceberg/issues" -[tool.uv.sources] -dagster_pyiceberg = { workspace = true } -dagster_pyiceberg_pandas = { workspace = true } -dagster_pyiceberg_polars = { workspace = true } +[project.optional-dependencies] +daft = [ + "getdaft>=0.3.13", +] +polars = [ + "polars>=1.13.1", +] [tool.black] line-length = 88 @@ -83,12 +107,6 @@ docs = [ "mkdocstrings[python]>=0.26.2", ] dev = [ - # These are added as dev dependencies becuase they should be available - # when developing the project. - "dagster_pyiceberg", - "dagster_pyiceberg_pandas", - "dagster_pyiceberg_polars", - # END WORKSPACE_PACKAGES "pre-commit>=3.8.0", "ipykernel>=6.29.5", "pytest>=8.3.2", @@ -96,6 +114,5 @@ dev = [ "pytest-coverage>=0.0", "testcontainers[postgres]>=4.8.2", "psycopg2-binary>=2.9.10", - #"cachetools>=5.5.0", "dagit>=1.8.8", ] diff --git a/src/dagster_pyiceberg/__init__.py b/src/dagster_pyiceberg/__init__.py new file mode 100644 index 0000000..8915488 --- /dev/null +++ b/src/dagster_pyiceberg/__init__.py @@ -0,0 +1,247 @@ +from typing import Sequence + +# from dagster._core.libraries import DagsterLibraryRegistry +from dagster._core.storage.db_io_manager import DbTypeHandler + +from dagster_pyiceberg.config import ( + IcebergRestCatalogConfig as IcebergRestCatalogConfig, +) +from dagster_pyiceberg.config import IcebergSqlCatalogConfig as IcebergSqlCatalogConfig +from dagster_pyiceberg.handler import IcebergDaftTypeHandler as IcebergDaftTypeHandler +from dagster_pyiceberg.handler import ( + IcebergPolarsTypeHandler as IcebergPolarsTypeHandler, +) +from dagster_pyiceberg.handler import ( + IcebergPyArrowTypeHandler as IcebergPyArrowTypeHandler, +) +from dagster_pyiceberg.io_manager import IcebergIOManager as IcebergIOManager +from dagster_pyiceberg.resource import IcebergTableResource as IcebergTableResource +from dagster_pyiceberg.version import __version__ as __version__ + + +class IcebergPyarrowIOManager(IcebergIOManager): + """An IO manager definition that reads inputs from and writes outputs to Iceberg tables using PyArrow. + + Examples: + .. code-block:: python + + import pandas as pd + import pyarrow as pa + from dagster import Definitions, asset + from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig + + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" + CATALOG_WAREHOUSE = ( + "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" + ) + + resources = { + "io_manager": IcebergPyarrowIOManager( + name="test", + config=IcebergSqlCatalogConfig( + properties={"uri": CATALOG_URI, "warehouse": CATALOG_WAREHOUSE} + ), + schema="dagster", + ) + } + + @asset + def iris_dataset() -> pd.DataFrame: + pa.Table.from_pandas( + pd.read_csv( + "https://docs.dagster.io/assets/iris.csv", + names=[ + "sepal_length_cm", + "sepal_width_cm", + "petal_length_cm", + "petal_width_cm", + "species", + ], + ) + ) + + defs = Definitions(assets=[iris_dataset], resources=resources) + + If you do not provide a schema, Dagster will determine a schema based on the assets and ops using + the I/O Manager. For assets, the schema will be determined from the asset key, as in the above example. + For ops, the schema can be specified by including a "schema" entry in output metadata. If none + of these is provided, the schema will default to "public". + + .. code-block:: python + + @op( + out={"my_table": Out(metadata={"schema": "my_schema"})} + ) + def make_my_table() -> pd.DataFrame: + ... + + To only use specific columns of a table as input to a downstream op or asset, add the metadata "columns" to the + In or AssetIn. + + .. code-block:: python + + @asset( + ins={"my_table": AssetIn("my_table", metadata={"columns": ["a"]})} + ) + def my_table_a(my_table: pd.DataFrame): + # my_table will just contain the data from column "a" + ... + + """ + + @staticmethod + def type_handlers() -> Sequence[DbTypeHandler]: + return [IcebergPyArrowTypeHandler()] + + +class IcebergPolarsIOManager(IcebergIOManager): + """An IO manager definition that reads inputs from and writes outputs to Iceberg tables using Polars. + + Examples: + .. code-block:: python + + import pandas as pd + import pyarrow as pa + from dagster import Definitions, asset + from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig + + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" + CATALOG_WAREHOUSE = ( + "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" + ) + + resources = { + "io_manager": IcebergPyarrowIOManager( + name="test", + config=IcebergSqlCatalogConfig( + properties={"uri": CATALOG_URI, "warehouse": CATALOG_WAREHOUSE} + ), + schema="dagster", + ) + } + + @asset + def iris_dataset() -> pd.DataFrame: + pa.Table.from_pandas( + pd.read_csv( + "https://docs.dagster.io/assets/iris.csv", + names=[ + "sepal_length_cm", + "sepal_width_cm", + "petal_length_cm", + "petal_width_cm", + "species", + ], + ) + ) + + defs = Definitions(assets=[iris_dataset], resources=resources) + + If you do not provide a schema, Dagster will determine a schema based on the assets and ops using + the I/O Manager. For assets, the schema will be determined from the asset key, as in the above example. + For ops, the schema can be specified by including a "schema" entry in output metadata. If none + of these is provided, the schema will default to "public". + + .. code-block:: python + + @op( + out={"my_table": Out(metadata={"schema": "my_schema"})} + ) + def make_my_table() -> pd.DataFrame: + ... + + To only use specific columns of a table as input to a downstream op or asset, add the metadata "columns" to the + In or AssetIn. + + .. code-block:: python + + @asset( + ins={"my_table": AssetIn("my_table", metadata={"columns": ["a"]})} + ) + def my_table_a(my_table: pd.DataFrame): + # my_table will just contain the data from column "a" + ... + + """ + + @staticmethod + def type_handlers() -> Sequence[DbTypeHandler]: + return [IcebergPolarsTypeHandler()] + + +class IcebergDaftIOManager(IcebergIOManager): + """An IO manager definition that reads inputs from and writes outputs to Iceberg tables using Daft. + + Examples: + .. code-block:: python + + import pandas as pd + import pyarrow as pa + from dagster import Definitions, asset + from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig + + CATALOG_URI = "sqlite:////home/vscode/workspace/.tmp/examples/select_columns/catalog.db" + CATALOG_WAREHOUSE = ( + "file:///home/vscode/workspace/.tmp/examples/select_columns/warehouse" + ) + + resources = { + "io_manager": IcebergPyarrowIOManager( + name="test", + config=IcebergSqlCatalogConfig( + properties={"uri": CATALOG_URI, "warehouse": CATALOG_WAREHOUSE} + ), + schema="dagster", + ) + } + + @asset + def iris_dataset() -> pd.DataFrame: + pa.Table.from_pandas( + pd.read_csv( + "https://docs.dagster.io/assets/iris.csv", + names=[ + "sepal_length_cm", + "sepal_width_cm", + "petal_length_cm", + "petal_width_cm", + "species", + ], + ) + ) + + defs = Definitions(assets=[iris_dataset], resources=resources) + + If you do not provide a schema, Dagster will determine a schema based on the assets and ops using + the I/O Manager. For assets, the schema will be determined from the asset key, as in the above example. + For ops, the schema can be specified by including a "schema" entry in output metadata. If none + of these is provided, the schema will default to "public". + + .. code-block:: python + + @op( + out={"my_table": Out(metadata={"schema": "my_schema"})} + ) + def make_my_table() -> pd.DataFrame: + ... + + To only use specific columns of a table as input to a downstream op or asset, add the metadata "columns" to the + In or AssetIn. + + .. code-block:: python + + @asset( + ins={"my_table": AssetIn("my_table", metadata={"columns": ["a"]})} + ) + def my_table_a(my_table: pd.DataFrame): + # my_table will just contain the data from column "a" + ... + + """ + + @staticmethod + def type_handlers() -> Sequence[DbTypeHandler]: + return [IcebergDaftTypeHandler()] + + +# DagsterLibraryRegistry.register("dagster-pyiceberg", __version__) diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_db_io_manager/__init__.py b/src/dagster_pyiceberg/_db_io_manager/__init__.py similarity index 100% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_db_io_manager/__init__.py rename to src/dagster_pyiceberg/_db_io_manager/__init__.py diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_db_io_manager/io_manager.py b/src/dagster_pyiceberg/_db_io_manager/io_manager.py similarity index 100% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_db_io_manager/io_manager.py rename to src/dagster_pyiceberg/_db_io_manager/io_manager.py diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_db_io_manager/utils.py b/src/dagster_pyiceberg/_db_io_manager/utils.py similarity index 99% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_db_io_manager/utils.py rename to src/dagster_pyiceberg/_db_io_manager/utils.py index 9be4af2..8ae4a23 100644 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_db_io_manager/utils.py +++ b/src/dagster_pyiceberg/_db_io_manager/utils.py @@ -9,9 +9,10 @@ ) from dagster._core.definitions.time_window_partitions import TimeWindow from dagster._core.storage.db_io_manager import TablePartitionDimension -from dagster_pyiceberg._utils.transforms import date_diff from pendulum import instance as pdi +from dagster_pyiceberg._utils.transforms import date_diff + class MultiTimePartitionsChecker: diff --git a/src/dagster_pyiceberg/_utils/__init__.py b/src/dagster_pyiceberg/_utils/__init__.py new file mode 100644 index 0000000..6392c04 --- /dev/null +++ b/src/dagster_pyiceberg/_utils/__init__.py @@ -0,0 +1,11 @@ +from dagster_pyiceberg._utils.io import CatalogTypes as CatalogTypes +from dagster_pyiceberg._utils.io import table_writer as table_writer +from dagster_pyiceberg._utils.partitions import ( + DagsterPartitionToDaftSqlPredicateMapper as DagsterPartitionToDaftSqlPredicateMapper, +) +from dagster_pyiceberg._utils.partitions import ( + DagsterPartitionToPolarsSqlPredicateMapper as DagsterPartitionToPolarsSqlPredicateMapper, +) +from dagster_pyiceberg._utils.partitions import ( + DagsterPartitionToPyIcebergExpressionMapper as DagsterPartitionToPyIcebergExpressionMapper, +) diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/io.py b/src/dagster_pyiceberg/_utils/io.py similarity index 86% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/io.py rename to src/dagster_pyiceberg/_utils/io.py index 232d16c..2311cfb 100644 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/io.py +++ b/src/dagster_pyiceberg/_utils/io.py @@ -1,17 +1,9 @@ -from typing import Dict, List, Optional, Sequence, Tuple, Union +from typing import Dict, List, Optional, Sequence, Union import pyarrow as pa from dagster._core.storage.db_io_manager import TablePartitionDimension, TableSlice -from dagster_pyiceberg._utils.partitions import ( - partition_dimensions_to_filters, - update_table_partition_spec, -) -from dagster_pyiceberg._utils.retries import PyIcebergOperationWithRetry -from dagster_pyiceberg._utils.schema import update_table_schema -from dagster_pyiceberg.version import __version__ as dagster_pyiceberg_version from pyiceberg import __version__ as iceberg_version from pyiceberg import expressions as E -from pyiceberg import table from pyiceberg import table as iceberg_table from pyiceberg.catalog.rest import RestCatalog from pyiceberg.catalog.sql import SqlCatalog @@ -19,6 +11,14 @@ from pyiceberg.partitioning import PartitionSpec from pyiceberg.schema import Schema +from dagster_pyiceberg._utils.partitions import ( + DagsterPartitionToPyIcebergExpressionMapper, + update_table_partition_spec, +) +from dagster_pyiceberg._utils.retries import PyIcebergOperationWithRetry +from dagster_pyiceberg._utils.schema import update_table_schema +from dagster_pyiceberg.version import __version__ as dagster_pyiceberg_version + CatalogTypes = Union[SqlCatalog, RestCatalog] @@ -109,7 +109,7 @@ def table_writer( row_filter: E.BooleanExpression if partition_dimensions is not None: - row_filter = get_row_filter( + row_filter = get_expression_row_filter( iceberg_table_schema=table.schema(), iceberg_partition_spec=table.spec(), dagster_partition_dimensions=partition_dimensions, @@ -129,42 +129,18 @@ def table_writer( ) -def table_reader(table_slice: TableSlice, catalog: CatalogTypes) -> table.DataScan: - """Reads a table slice from an iceberg table and slices it according to partitioning (if present)""" - if table_slice.partition_dimensions is None: - raise ValueError( - "Partition dimensions are not set. Please set the 'partition_dimensions' field in the TableSlice." - ) - table_name = f"{table_slice.schema}.{table_slice.table}" - table = catalog.load_table(table_name) - selected_fields: Tuple[str, ...] = ( - tuple(table_slice.columns) if table_slice.columns is not None else ("*",) - ) - row_filter: E.BooleanExpression - if table_slice.partition_dimensions: - row_filter = get_row_filter( - iceberg_table_schema=table.schema(), - iceberg_partition_spec=table.spec(), - dagster_partition_dimensions=table_slice.partition_dimensions, - ) - else: - row_filter = iceberg_table.ALWAYS_TRUE - - return table.scan(row_filter=row_filter, selected_fields=selected_fields) - - -def get_row_filter( +def get_expression_row_filter( iceberg_table_schema: Schema, iceberg_partition_spec: PartitionSpec, dagster_partition_dimensions: Sequence[TablePartitionDimension], ) -> E.BooleanExpression: """Construct an iceberg row filter based on dagster partition dimensions that can be used to overwrite those specific rows in the iceberg table.""" - partition_filters = partition_dimensions_to_filters( + partition_filters = DagsterPartitionToPyIcebergExpressionMapper( partition_dimensions=dagster_partition_dimensions, table_schema=iceberg_table_schema, table_partition_spec=iceberg_partition_spec, - ) + ).partition_dimensions_to_filters() return ( E.And(*partition_filters) if len(partition_filters) > 1 @@ -177,7 +153,7 @@ def create_table_if_not_exists( table_path: str, schema: pa.Schema, properties: Dict[str, str], -) -> table.Table: +) -> iceberg_table.Table: """Creates an iceberg table and retries on failure Args: @@ -217,7 +193,7 @@ def operation(self, table_path: str, schema: pa.Schema, properties: Dict[str, st def overwrite_table( - table: table.Table, + table: iceberg_table.Table, data: pa.Table, overwrite_filter: Union[E.BooleanExpression, str], snapshot_properties: Optional[Dict[str, str]] = None, diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/partitions.py b/src/dagster_pyiceberg/_utils/partitions.py similarity index 67% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/partitions.py rename to src/dagster_pyiceberg/_utils/partitions.py index a46bf0d..b576fba 100644 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/partitions.py +++ b/src/dagster_pyiceberg/_utils/partitions.py @@ -1,11 +1,21 @@ import datetime as dt import itertools -from typing import Dict, Iterable, List, Optional, Sequence, Set, Union, cast +from abc import abstractmethod +from typing import ( + Dict, + Generic, + Iterable, + List, + Optional, + Sequence, + Set, + TypeVar, + Union, + cast, +) from dagster._core.definitions.time_window_partitions import TimeWindow from dagster._core.storage.db_io_manager import TablePartitionDimension, TableSlice -from dagster_pyiceberg._utils.retries import PyIcebergOperationWithRetry -from dagster_pyiceberg._utils.transforms import diff_to_transformation from pyiceberg import expressions as E from pyiceberg import types as T from pyiceberg.partitioning import PartitionField, PartitionSpec @@ -14,9 +24,190 @@ from pyiceberg.table.update.spec import UpdateSpec from pyiceberg.transforms import IdentityTransform +from dagster_pyiceberg._utils.retries import PyIcebergOperationWithRetry +from dagster_pyiceberg._utils.transforms import diff_to_transformation + time_partition_dt_types = (T.TimestampType, T.DateType) partition_types = T.StringType +K = TypeVar("K") + + +class DagsterPartitionToPredicateMapper(Generic[K]): + + def __init__( + self, + partition_dimensions: Iterable[TablePartitionDimension], + table_schema: Schema, + table_partition_spec: Optional[PartitionSpec] = None, + ): + self.partition_dimensions = partition_dimensions + self.table_schema = table_schema + self.table_partition_spec = table_partition_spec + + @abstractmethod + def _partition_filters_to_predicates( + self, partition_expr: str, partition_filters: Sequence[str] + ) -> K: ... + + @abstractmethod + def _time_window_partition_filters_to_predicates( + self, + partition_expr: str, + start_dt: Union[dt.date, dt.datetime], + end_dt: Union[dt.date, dt.datetime], + ) -> K: ... + + def _partition_filter( + self, + table_partition: TablePartitionDimension, + ) -> K: + return self._partition_filters_to_predicates( + partition_expr=table_partition.partition_expr, + partition_filters=cast(Sequence[str], table_partition.partitions), + ) + + def _time_window_partition_filter( + self, + table_partition: TablePartitionDimension, + iceberg_partition_spec_field_type: Union[ + T.DateType, T.TimestampType, T.TimeType, T.TimestamptzType + ], + ) -> K: + """Create an iceberg filter for a dagster time window partition + + Args: + table_partition (TablePartitionDimension): Dagster time window partition + iceberg_partition_spec_field_type (Union[T.DateType, T.TimestampType, T.TimeType, T.TimestamptzType]): Iceberg field type + required to correctly format the partition values + + Returns: + List[E.BooleanExpression]: List of iceberg filters with start and end dates + """ + partition = cast(TimeWindow, table_partition.partitions) + start_dt, end_dt = partition + if isinstance(start_dt, dt.datetime): + start_dt = start_dt.replace(tzinfo=None) + end_dt = end_dt.replace(tzinfo=None) + if isinstance(iceberg_partition_spec_field_type, T.DateType): + # Internally, PyIceberg uses dt.date.fromisoformat to parse dates. + # Dagster will pass dt.datetime objects in time window partitions. + # but dt.date.fromisoformat cannot parse dt.datetime.isoformat strings + start_dt = start_dt.date() + end_dt = end_dt.date() + return self._time_window_partition_filters_to_predicates( + partition_expr=table_partition.partition_expr, + start_dt=start_dt, + end_dt=end_dt, + ) + + def partition_dimensions_to_filters(self) -> List[K]: + """Converts dagster partitions to iceberg filters""" + predicates = [] + partition_spec_fields: Optional[Dict[int, str]] = None + if self.table_partition_spec is not None: # Only None when writing new tables + partition_spec_fields = map_partition_spec_to_fields( + partition_spec=self.table_partition_spec, table_schema=self.table_schema + ) + for partition_dimension in self.partition_dimensions: + field = self.table_schema.find_field(partition_dimension.partition_expr) + if partition_spec_fields is not None: + if field.field_id not in partition_spec_fields.keys(): + raise ValueError( + f"Table is not partitioned by field '{field.name}' with id" + f"'{field.field_id}'. Available partition fields: " + f"{partition_spec_fields}" + ) + # TODO: add timestamp tz type and time type + predicate_: K + if isinstance(field.field_type, time_partition_dt_types): + predicate_ = self._time_window_partition_filter( + table_partition=partition_dimension, + iceberg_partition_spec_field_type=field.field_type, + ) + elif isinstance(field.field_type, partition_types): + predicate_ = self._partition_filter(table_partition=partition_dimension) + else: + raise ValueError( + f"Partitioning by field type '{str(field.field_type)}' not supported" + ) + predicates.append(predicate_) + return predicates + + +class DagsterPartitionToPyIcebergExpressionMapper( + DagsterPartitionToPredicateMapper[E.BooleanExpression] +): + + def _partition_filters_to_predicates( + self, partition_expr: str, partition_filters: Sequence[str] + ) -> E.BooleanExpression | E.LiteralPredicate[str]: + predicates = [E.EqualTo(partition_expr, p) for p in partition_filters] + if len(predicates) > 1: + return E.Or(*predicates) + else: + return predicates[0] + + def _time_window_partition_filters_to_predicates( + self, + partition_expr: str, + start_dt: Union[dt.date, dt.datetime], + end_dt: Union[dt.date, dt.datetime], + ) -> E.BooleanExpression: + return E.And( + *[ + E.GreaterThanOrEqual(partition_expr, start_dt.isoformat()), + E.LessThan(partition_expr, end_dt.isoformat()), + ] + ) + + +class DagsterPartitionToSqlPredicateMapper(DagsterPartitionToPredicateMapper[str]): + + def _partition_filters_to_predicates( + self, partition_expr: str, partition_filters: Sequence[str] + ) -> str: + predicates = [f"{partition_expr} = '{p}'" for p in partition_filters] + if len(predicates) > 1: + return f"({' OR '.join(predicates)})" + else: + return predicates[0] + + @abstractmethod + def _time_window_partition_filters_to_predicates( + self, + partition_expr: str, + start_dt: Union[dt.date, dt.datetime], + end_dt: Union[dt.date, dt.datetime], + ) -> str: ... + + +class DagsterPartitionToPolarsSqlPredicateMapper(DagsterPartitionToSqlPredicateMapper): + + def _time_window_partition_filters_to_predicates( + self, + partition_expr: str, + start_dt: Union[dt.date, dt.datetime], + end_dt: Union[dt.date, dt.datetime], + ) -> str: + return f"{partition_expr} >= '{start_dt.isoformat()}' AND {partition_expr} < '{end_dt.isoformat()}'" + + +class DagsterPartitionToDaftSqlPredicateMapper(DagsterPartitionToSqlPredicateMapper): + + def _time_window_partition_filters_to_predicates( + self, + partition_expr: str, + start_dt: Union[dt.date, dt.datetime], + end_dt: Union[dt.date, dt.datetime], + ) -> str: + # See https://docs.rs/chrono/latest/chrono/format/strftime/index.html + parse_fmt = "%+" if isinstance(start_dt, dt.datetime) else "%F" + return ( + f"{partition_expr} >= to_date('{start_dt.isoformat()}', '{parse_fmt}') " + f"AND {partition_expr} < to_date('{end_dt.isoformat()}', '{parse_fmt}')" + ) + def update_table_partition_spec( table: Table, table_slice: TableSlice, partition_spec_update_mode: str @@ -333,88 +524,3 @@ def map_partition_spec_to_fields(partition_spec: PartitionSpec, table_schema: Sc ) partition_spec_fields[field.source_id] = field_name return partition_spec_fields - - -def partition_dimensions_to_filters( - partition_dimensions: Iterable[TablePartitionDimension], - table_schema: Schema, - table_partition_spec: Optional[PartitionSpec] = None, -) -> List[E.BooleanExpression]: - """Converts dagster partitions to iceberg filters""" - partition_filters = [] - partition_spec_fields: Optional[Dict[int, str]] = None - if table_partition_spec is not None: # Only None when writing new tables - partition_spec_fields = map_partition_spec_to_fields( - partition_spec=table_partition_spec, table_schema=table_schema - ) - for partition_dimension in partition_dimensions: - field = table_schema.find_field(partition_dimension.partition_expr) - if partition_spec_fields is not None: - if field.field_id not in partition_spec_fields.keys(): - raise ValueError( - f"Table is not partitioned by field '{field.name}' with id '{field.field_id}'. Available partition fields: {partition_spec_fields}" - ) - # TODO: add timestamp tz type and time type - filter_: Union[E.BooleanExpression, List[E.BooleanExpression]] - if isinstance(field.field_type, time_partition_dt_types): - filter_ = time_window_partition_filter( - table_partition=partition_dimension, - iceberg_partition_spec_field_type=field.field_type, - ) - elif isinstance(field.field_type, partition_types): - filter_ = partition_filter(table_partition=partition_dimension) - else: - raise ValueError( - f"Partitioning by field type '{str(field.field_type)}' not supported" - ) - ( - partition_filters.append(filter_) - if isinstance(filter_, E.BooleanExpression) - else partition_filters.extend(filter_) - ) - return partition_filters - - -def partition_filter( - table_partition: TablePartitionDimension, -) -> E.BooleanExpression: - partition = cast(Sequence[str], table_partition.partitions) - if len(partition) > 1: - return E.Or(*[E.EqualTo(table_partition.partition_expr, p) for p in partition]) - else: - return E.EqualTo(table_partition.partition_expr, table_partition.partitions[0]) # type: ignore - - -def time_window_partition_filter( - table_partition: TablePartitionDimension, - iceberg_partition_spec_field_type: Union[ - T.DateType, T.TimestampType, T.TimeType, T.TimestamptzType - ], -) -> E.BooleanExpression: - """Create an iceberg filter for a dagster time window partition - - Args: - table_partition (TablePartitionDimension): Dagster time window partition - iceberg_partition_spec_field_type (Union[T.DateType, T.TimestampType, T.TimeType, T.TimestamptzType]): Iceberg field type - required to correctly format the partition values - - Returns: - List[E.BooleanExpression]: List of iceberg filters with start and end dates - """ - partition = cast(TimeWindow, table_partition.partitions) - start_dt, end_dt = partition - if isinstance(start_dt, dt.datetime): - start_dt = start_dt.replace(tzinfo=None) - end_dt = end_dt.replace(tzinfo=None) - if isinstance(iceberg_partition_spec_field_type, T.DateType): - # Internally, PyIceberg uses dt.date.fromisoformat to parse dates. - # Dagster will pass dt.datetime objects in time window partitions. - # but dt.date.fromisoformat cannot parse dt.datetime.isoformat strings - start_dt = start_dt.date() - end_dt = end_dt.date() - return E.And( - *[ - E.GreaterThanOrEqual(table_partition.partition_expr, start_dt.isoformat()), - E.LessThan(table_partition.partition_expr, end_dt.isoformat()), - ] - ) diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/retries.py b/src/dagster_pyiceberg/_utils/retries.py similarity index 100% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/retries.py rename to src/dagster_pyiceberg/_utils/retries.py diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/schema.py b/src/dagster_pyiceberg/_utils/schema.py similarity index 99% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/schema.py rename to src/dagster_pyiceberg/_utils/schema.py index 7449b11..166506b 100644 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/schema.py +++ b/src/dagster_pyiceberg/_utils/schema.py @@ -2,9 +2,10 @@ from typing import List import pyarrow as pa -from dagster_pyiceberg._utils.retries import PyIcebergOperationWithRetry from pyiceberg import table +from dagster_pyiceberg._utils.retries import PyIcebergOperationWithRetry + def update_table_schema( table: table.Table, new_table_schema: pa.Schema, schema_update_mode: str diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/transforms.py b/src/dagster_pyiceberg/_utils/transforms.py similarity index 100% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/_utils/transforms.py rename to src/dagster_pyiceberg/_utils/transforms.py diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/config.py b/src/dagster_pyiceberg/config.py similarity index 100% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/config.py rename to src/dagster_pyiceberg/config.py diff --git a/src/dagster_pyiceberg/handler.py b/src/dagster_pyiceberg/handler.py new file mode 100644 index 0000000..5916aa1 --- /dev/null +++ b/src/dagster_pyiceberg/handler.py @@ -0,0 +1,216 @@ +from abc import abstractmethod +from typing import Generic, Sequence, Tuple, Type, TypeVar, Union, cast + +import daft as da +import polars as pl +import pyarrow as pa +from dagster import InputContext, MetadataValue, OutputContext, TableColumn, TableSchema +from dagster._core.storage.db_io_manager import DbTypeHandler, TableSlice +from pyiceberg import expressions as E +from pyiceberg import table as ibt +from pyiceberg.table.snapshots import Snapshot + +from dagster_pyiceberg._utils import ( + CatalogTypes, + DagsterPartitionToDaftSqlPredicateMapper, + DagsterPartitionToPolarsSqlPredicateMapper, + DagsterPartitionToPyIcebergExpressionMapper, + table_writer, +) + +U = TypeVar("U") + +ArrowTypes = Union[pa.Table, pa.RecordBatchReader] +PolarsTypes = Union[pl.LazyFrame, pl.DataFrame] + + +class IcebergBaseTypeHandler(DbTypeHandler[U], Generic[U]): + + @abstractmethod + def to_data_frame( + self, table: ibt.Table, table_slice: TableSlice, target_type: type + ) -> U: + pass + + @abstractmethod + def to_arrow(self, obj: U) -> pa.Table: + pass + + def handle_output( + self, + context: OutputContext, + table_slice: TableSlice, + obj: U, + connection: CatalogTypes, + ): + """Stores pyarrow types in Iceberg table""" + metadata = context.definition_metadata or {} # noqa + resource_config = context.resource_config or {} + + # NB: not checking properties here, except for protected properties + table_properties_usr = metadata.get("table_properties", {}) + for k, _ in table_properties_usr.items(): + if k in [ + "created-by", + "run-id", + "dagster-pyiceberg-version", + "pyiceberg-version", + ]: + raise KeyError( + f"Table properties cannot contain the following keys: {k}" + ) + + partition_spec_update_mode = cast( + str, resource_config["partition_spec_update_mode"] + ) + schema_update_mode = cast(str, resource_config["schema_update_mode"]) + + table_writer( + table_slice=table_slice, + data=self.to_arrow(obj), + catalog=connection, + partition_spec_update_mode=partition_spec_update_mode, + schema_update_mode=schema_update_mode, + dagster_run_id=context.run_id, + dagster_partition_key=( + context.partition_key if context.has_asset_partitions else None + ), + table_properties=table_properties_usr, + ) + + table_ = connection.load_table(f"{table_slice.schema}.{table_slice.table}") + + current_snapshot = cast(Snapshot, table_.current_snapshot()) + + context.add_output_metadata( + { + "table_columns": MetadataValue.table_schema( + TableSchema( + columns=[ + TableColumn(name=f["name"], type=str(f["type"])) + for f in table_.schema().model_dump()["fields"] + ] + ) + ), + **current_snapshot.model_dump(), + } + ) + + def load_input( + self, + context: InputContext, + table_slice: TableSlice, + connection: CatalogTypes, + ) -> U: + """Loads the input using a dataframe implmentation""" + return self.to_data_frame( + table=connection.load_table(f"{table_slice.schema}.{table_slice.table}"), + table_slice=table_slice, + target_type=context.dagster_type.typing_type, + ) + + +class IcebergPyArrowTypeHandler(IcebergBaseTypeHandler[ArrowTypes]): + """Type handler that converts data between Iceberg tables and pyarrow Tables""" + + def to_data_frame( + self, table: ibt.Table, table_slice: TableSlice, target_type: Type[ArrowTypes] + ) -> ArrowTypes: + selected_fields: Tuple[str, ...] = ( + tuple(table_slice.columns) if table_slice.columns is not None else ("*",) + ) + row_filter: E.BooleanExpression + if table_slice.partition_dimensions: + expressions = DagsterPartitionToPyIcebergExpressionMapper( + partition_dimensions=table_slice.partition_dimensions, + table_schema=table.schema(), + table_partition_spec=table.spec(), + ).partition_dimensions_to_filters() + row_filter = E.And(*expressions) if len(expressions) > 1 else expressions[0] + else: + row_filter = ibt.ALWAYS_TRUE + + table_scan = table.scan(row_filter=row_filter, selected_fields=selected_fields) + + return ( + table_scan.to_arrow() + if target_type == pa.Table + else table_scan.to_arrow_batch_reader() + ) + + def to_arrow(self, obj: ArrowTypes) -> pa.Table: + return obj + + @property + def supported_types(self) -> Sequence[Type[object]]: + return (pa.Table, pa.RecordBatchReader) + + +class IcebergPolarsTypeHandler(IcebergBaseTypeHandler[PolarsTypes]): + """Type handler that converts data between Iceberg tables and polars DataFrames""" + + def to_data_frame( + self, table: ibt.Table, table_slice: TableSlice, target_type: Type[PolarsTypes] + ) -> PolarsTypes: + selected_fields: str = ( + ",".join(table_slice.columns) if table_slice.columns is not None else "*" + ) + row_filter: str | None = None + if table_slice.partition_dimensions: + expressions = DagsterPartitionToPolarsSqlPredicateMapper( + partition_dimensions=table_slice.partition_dimensions, + table_schema=table.schema(), + table_partition_spec=table.spec(), + ).partition_dimensions_to_filters() + row_filter = " AND ".join(expressions) + + pdf = pl.scan_iceberg(source=table) + + stmt = f"SELECT {selected_fields} FROM self" + if row_filter is not None: + stmt += f"\nWHERE {row_filter}" + return pdf.sql(stmt) if target_type == pl.LazyFrame else pdf.sql(stmt).collect() + + def to_arrow(self, obj: PolarsTypes) -> pa.Table: + if isinstance(obj, pl.LazyFrame): + return obj.collect().to_arrow() + else: + return obj.to_arrow() + + @property + def supported_types(self) -> Sequence[Type[object]]: + return (pl.LazyFrame, pl.DataFrame) + + +class IcebergDaftTypeHandler(IcebergBaseTypeHandler[da.DataFrame]): + """Type handler that converts data between Iceberg tables and polars DataFrames""" + + def to_data_frame( + self, table: ibt.Table, table_slice: TableSlice, target_type: Type[da.DataFrame] + ) -> da.DataFrame: + selected_fields: str = ( + ",".join(table_slice.columns) if table_slice.columns is not None else "*" + ) + row_filter: str | None = None + if table_slice.partition_dimensions: + expressions = DagsterPartitionToDaftSqlPredicateMapper( + partition_dimensions=table_slice.partition_dimensions, + table_schema=table.schema(), + table_partition_spec=table.spec(), + ).partition_dimensions_to_filters() + row_filter = " AND ".join(expressions) + + ddf = table.to_daft() # type: ignore # noqa + + stmt = f"SELECT {selected_fields} FROM ddf" + if row_filter is not None: + stmt += f"\nWHERE {row_filter}" + + return da.sql(stmt) + + def to_arrow(self, obj: da.DataFrame) -> pa.Table: + return obj.to_arrow() + + @property + def supported_types(self) -> Sequence[Type[object]]: + return [da.DataFrame] diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/io_manager.py b/src/dagster_pyiceberg/io_manager.py similarity index 99% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/io_manager.py rename to src/dagster_pyiceberg/io_manager.py index 95e892e..b3bdd63 100644 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/io_manager.py +++ b/src/dagster_pyiceberg/io_manager.py @@ -22,15 +22,16 @@ TablePartitionDimension, TableSlice, ) +from pydantic import Field +from pyiceberg.catalog.rest import RestCatalog +from pyiceberg.catalog.sql import SqlCatalog + from dagster_pyiceberg._db_io_manager import CustomDbIOManager from dagster_pyiceberg.config import ( # noqa IcebergRestCatalogConfig, IcebergSqlCatalogConfig, ) from dagster_pyiceberg.handler import CatalogTypes -from pydantic import Field -from pyiceberg.catalog.rest import RestCatalog -from pyiceberg.catalog.sql import SqlCatalog class PartitionSpecUpdateMode(enum.Enum): diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/py.typed b/src/dagster_pyiceberg/py.typed similarity index 100% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/py.typed rename to src/dagster_pyiceberg/py.typed diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/resource.py b/src/dagster_pyiceberg/resource.py similarity index 99% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/resource.py rename to src/dagster_pyiceberg/resource.py index d7856b6..dc142c9 100644 --- a/packages/dagster_pyiceberg/src/dagster_pyiceberg/resource.py +++ b/src/dagster_pyiceberg/resource.py @@ -1,13 +1,14 @@ from typing import Optional, Union, cast from dagster import ConfigurableResource +from pydantic import Field +from pyiceberg.table import Table + from dagster_pyiceberg.config import IcebergRestCatalogConfig, IcebergSqlCatalogConfig from dagster_pyiceberg.io_manager import ( _connect_to_catalog, _IcebergMetastoreCatalogConfig, ) -from pydantic import Field -from pyiceberg.table import Table SupportedCatalogConfigs = Union[IcebergRestCatalogConfig, IcebergSqlCatalogConfig] diff --git a/packages/dagster_pyiceberg/src/dagster_pyiceberg/version.py b/src/dagster_pyiceberg/version.py similarity index 100% rename from packages/dagster_pyiceberg/src/dagster_pyiceberg/version.py rename to src/dagster_pyiceberg/version.py diff --git a/tests/dagster_pyiceberg/_db_io_manager/conftest.py b/tests/_db_io_manager/conftest.py similarity index 100% rename from tests/dagster_pyiceberg/_db_io_manager/conftest.py rename to tests/_db_io_manager/conftest.py diff --git a/tests/dagster_pyiceberg/_db_io_manager/test_db_io_manager.py b/tests/_db_io_manager/test_db_io_manager.py similarity index 99% rename from tests/dagster_pyiceberg/_db_io_manager/test_db_io_manager.py rename to tests/_db_io_manager/test_db_io_manager.py index 7cb3804..3c9899b 100644 --- a/tests/dagster_pyiceberg/_db_io_manager/test_db_io_manager.py +++ b/tests/_db_io_manager/test_db_io_manager.py @@ -26,6 +26,7 @@ asset, materialize, ) + from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig diff --git a/tests/dagster_pyiceberg/_db_io_manager/test_db_io_manager_utils.py b/tests/_db_io_manager/test_db_io_manager_utils.py similarity index 99% rename from tests/dagster_pyiceberg/_db_io_manager/test_db_io_manager_utils.py rename to tests/_db_io_manager/test_db_io_manager_utils.py index d35daba..006b318 100644 --- a/tests/dagster_pyiceberg/_db_io_manager/test_db_io_manager_utils.py +++ b/tests/_db_io_manager/test_db_io_manager_utils.py @@ -4,6 +4,7 @@ import pytest from dagster import AssetKey, MultiPartitionKey, MultiPartitionsDefinition, TimeWindow from dagster._core.storage.db_io_manager import TablePartitionDimension + from dagster_pyiceberg._db_io_manager import utils diff --git a/tests/dagster_pyiceberg/_utils/conftest.py b/tests/_utils/conftest.py similarity index 100% rename from tests/dagster_pyiceberg/_utils/conftest.py rename to tests/_utils/conftest.py diff --git a/tests/dagster_pyiceberg/_utils/test_io.py b/tests/_utils/test_io.py similarity index 92% rename from tests/dagster_pyiceberg/_utils/test_io.py rename to tests/_utils/test_io.py index d158f8e..274e1b5 100644 --- a/tests/dagster_pyiceberg/_utils/test_io.py +++ b/tests/_utils/test_io.py @@ -6,35 +6,10 @@ import pytest from dagster._core.definitions.time_window_partitions import TimeWindow from dagster._core.storage.db_io_manager import TablePartitionDimension, TableSlice -from dagster_pyiceberg._utils import io from pyiceberg import expressions as E from pyiceberg.catalog.sql import SqlCatalog - -def test_partitioned_table_reader( - catalog: SqlCatalog, partitioned_table_slice: TableSlice -): - table_ = io.table_reader(partitioned_table_slice, catalog) - data_ = table_.to_arrow().to_pydict() - assert min(data_["timestamp"]) >= dt.datetime(2023, 1, 1, 0) - assert max(data_["timestamp"]) < dt.datetime(2023, 1, 1, 1) - assert list(set(data_["category"])) == ["A"] - - -def test_table_reader(catalog: SqlCatalog, table_slice: TableSlice): - table_ = io.table_reader(table_slice, catalog) - data_ = table_.to_arrow().to_pydict() - assert len(data_["timestamp"]) == 1440 - - -def test_table_reader_with_selected_columns( - catalog: SqlCatalog, table_slice_with_selected_columns: TableSlice -): - table_ = io.table_reader(table_slice_with_selected_columns, catalog) - data_ = table_.to_arrow().to_pydict() - assert len(data_["value"]) == 1440 - assert len(data_) == 1 - assert list(data_.keys()) == ["value"] +from dagster_pyiceberg._utils import io def test_table_writer(namespace: str, catalog: SqlCatalog, data: pa.Table): diff --git a/tests/dagster_pyiceberg/_utils/test_partitions.py b/tests/_utils/test_partitions.py similarity index 74% rename from tests/dagster_pyiceberg/_utils/test_partitions.py rename to tests/_utils/test_partitions.py index da7df00..d80e127 100644 --- a/tests/dagster_pyiceberg/_utils/test_partitions.py +++ b/tests/_utils/test_partitions.py @@ -4,7 +4,6 @@ import pytest from dagster._core.definitions.time_window_partitions import TimeWindow from dagster._core.storage.db_io_manager import TablePartitionDimension, TableSlice -from dagster_pyiceberg._utils import partitions from pyiceberg import expressions as E from pyiceberg import partitioning as iceberg_partitioning from pyiceberg import schema as iceberg_schema @@ -12,8 +11,27 @@ from pyiceberg import transforms from pyiceberg import types as T +from dagster_pyiceberg._utils import partitions + + +@pytest.fixture +def dagster_partition_to_pyiceberg_expression_mapper( + datetime_table_partition_dimension: TablePartitionDimension, + category_table_partition_dimension_multiple: TablePartitionDimension, + table_partitioned: iceberg_table.Table, +) -> partitions.DagsterPartitionToPyIcebergExpressionMapper: + return partitions.DagsterPartitionToPyIcebergExpressionMapper( + partition_dimensions=[ + datetime_table_partition_dimension, + category_table_partition_dimension_multiple, + ], + table_schema=table_partitioned.schema(), + table_partition_spec=table_partitioned.spec(), + ) + def test_time_window_partition_filter( + dagster_partition_to_pyiceberg_expression_mapper: partitions.DagsterPartitionToPyIcebergExpressionMapper, datetime_table_partition_dimension: TablePartitionDimension, ): expected_filter = E.And( @@ -22,23 +40,33 @@ def test_time_window_partition_filter( E.LessThan("timestamp", "2023-01-01T01:00:00"), ] ) - filter_ = partitions.time_window_partition_filter( - datetime_table_partition_dimension, T.TimestampType + filter_ = ( + dagster_partition_to_pyiceberg_expression_mapper._time_window_partition_filter( + datetime_table_partition_dimension, T.TimestampType + ) ) assert filter_ == expected_filter -def test_partition_filter(category_table_partition_dimension: TablePartitionDimension): +def test_partition_filter( + dagster_partition_to_pyiceberg_expression_mapper: partitions.DagsterPartitionToPyIcebergExpressionMapper, + category_table_partition_dimension: TablePartitionDimension, +): expected_filter = E.EqualTo("category", "A") - filter_ = partitions.partition_filter(category_table_partition_dimension) + filter_ = dagster_partition_to_pyiceberg_expression_mapper._partition_filter( + category_table_partition_dimension + ) assert filter_ == expected_filter def test_partition_filter_with_multiple( + dagster_partition_to_pyiceberg_expression_mapper: partitions.DagsterPartitionToPyIcebergExpressionMapper, category_table_partition_dimension_multiple: TablePartitionDimension, ): expected_filter = E.Or(*[E.EqualTo("category", "A"), E.EqualTo("category", "B")]) - filter_ = partitions.partition_filter(category_table_partition_dimension_multiple) + filter_ = dagster_partition_to_pyiceberg_expression_mapper._partition_filter( + category_table_partition_dimension_multiple + ) assert filter_ == expected_filter @@ -47,7 +75,7 @@ def test_partition_dimensions_to_filters( category_table_partition_dimension: TablePartitionDimension, table_partitioned: iceberg_table.Table, ): - filters = partitions.partition_dimensions_to_filters( + mapper = partitions.DagsterPartitionToPyIcebergExpressionMapper( partition_dimensions=[ datetime_table_partition_dimension, category_table_partition_dimension, @@ -55,6 +83,7 @@ def test_partition_dimensions_to_filters( table_schema=table_partitioned.schema(), table_partition_spec=table_partitioned.spec(), ) + filters = mapper.partition_dimensions_to_filters() expected_filters = [ E.And( *[ @@ -68,17 +97,10 @@ def test_partition_dimensions_to_filters( def test_partition_dimensions_to_filters_multiple_categories( - datetime_table_partition_dimension: TablePartitionDimension, - category_table_partition_dimension_multiple: TablePartitionDimension, - table_partitioned: iceberg_table.Table, + dagster_partition_to_pyiceberg_expression_mapper: partitions.DagsterPartitionToPyIcebergExpressionMapper, ): - filters = partitions.partition_dimensions_to_filters( - partition_dimensions=[ - datetime_table_partition_dimension, - category_table_partition_dimension_multiple, - ], - table_schema=table_partitioned.schema(), - table_partition_spec=table_partitioned.spec(), + filters = ( + dagster_partition_to_pyiceberg_expression_mapper.partition_dimensions_to_filters() ) expected_filters = [ E.And( @@ -440,3 +462,94 @@ def test_update_table_partition_spec_with_retries( ) partitioned_table_slice.partition_dimensions assert mock_update_method.add_field.call_count == 7 + + +def test_dagster_partition_to_polars_sql_predicate_mapper( + datetime_table_partition_dimension: TablePartitionDimension, + category_table_partition_dimension: TablePartitionDimension, + table_partitioned: iceberg_table.Table, +): + mapper = partitions.DagsterPartitionToPolarsSqlPredicateMapper( + partition_dimensions=[ + datetime_table_partition_dimension, + category_table_partition_dimension, + ], + table_schema=table_partitioned.schema(), + table_partition_spec=table_partitioned.spec(), + ) + predicates = mapper.partition_dimensions_to_filters() + assert ( + predicates[0] + == "timestamp >= '2023-01-01T00:00:00' AND timestamp < '2023-01-01T01:00:00'" + ) + assert predicates[1] == "category = 'A'" + + +def test_dagster_partition_to_daft_sql_predicate_mapper( + datetime_table_partition_dimension: TablePartitionDimension, + category_table_partition_dimension: TablePartitionDimension, + table_partitioned: iceberg_table.Table, +): + mapper = partitions.DagsterPartitionToDaftSqlPredicateMapper( + partition_dimensions=[ + datetime_table_partition_dimension, + category_table_partition_dimension, + ], + table_schema=table_partitioned.schema(), + table_partition_spec=table_partitioned.spec(), + ) + predicates = mapper.partition_dimensions_to_filters() + assert ( + predicates[0] + == "timestamp >= to_date('2023-01-01T00:00:00', '%+') AND timestamp < to_date('2023-01-01T01:00:00', '%+')" + ) + assert predicates[1] == "category = 'A'" + + +def test_dagster_partition_to_sql_predicate_mapper_with_multiple_categories( + datetime_table_partition_dimension: TablePartitionDimension, + category_table_partition_dimension_multiple: TablePartitionDimension, + table_partitioned: iceberg_table.Table, +): + mapper = ( + partitions.DagsterPartitionToDaftSqlPredicateMapper( # Same for all sql mappers + partition_dimensions=[ + datetime_table_partition_dimension, + category_table_partition_dimension_multiple, + ], + table_schema=table_partitioned.schema(), + table_partition_spec=table_partitioned.spec(), + ) + ) + predicates = mapper.partition_dimensions_to_filters() + assert ( + predicates[0] + == "timestamp >= to_date('2023-01-01T00:00:00', '%+') AND timestamp < to_date('2023-01-01T01:00:00', '%+')" + ) + assert predicates[1] == "(category = 'A' OR category = 'B')" + + +def test_dagster_partition_to_pyiceberg_expression_mapper_with_multiple_categories( + datetime_table_partition_dimension: TablePartitionDimension, + category_table_partition_dimension_multiple: TablePartitionDimension, + table_partitioned: iceberg_table.Table, +): + mapper = partitions.DagsterPartitionToPyIcebergExpressionMapper( # Same for all sql mappers + partition_dimensions=[ + datetime_table_partition_dimension, + category_table_partition_dimension_multiple, + ], + table_schema=table_partitioned.schema(), + table_partition_spec=table_partitioned.spec(), + ) + expressions = mapper.partition_dimensions_to_filters() + expected_expressions = [ + E.And( + *[ + E.GreaterThanOrEqual("timestamp", "2023-01-01T00:00:00"), + E.LessThan("timestamp", "2023-01-01T01:00:00"), + ] + ), + E.Or(*[E.EqualTo("category", "A"), E.EqualTo("category", "B")]), + ] + assert expressions == expected_expressions diff --git a/tests/dagster_pyiceberg/_utils/test_schema.py b/tests/_utils/test_schema.py similarity index 99% rename from tests/dagster_pyiceberg/_utils/test_schema.py rename to tests/_utils/test_schema.py index 0c1b198..44730e0 100644 --- a/tests/dagster_pyiceberg/_utils/test_schema.py +++ b/tests/_utils/test_schema.py @@ -2,9 +2,10 @@ import pyarrow as pa import pytest -from dagster_pyiceberg._utils import schema from pyiceberg.table import Table +from dagster_pyiceberg._utils import schema + def test_schema_differ_removed_fields(): schema_current = pa.schema( diff --git a/tests/dagster_pyiceberg/_utils/test_transforms.py b/tests/_utils/test_transforms.py similarity index 99% rename from tests/dagster_pyiceberg/_utils/test_transforms.py rename to tests/_utils/test_transforms.py index 94325ae..bb4b82b 100644 --- a/tests/dagster_pyiceberg/_utils/test_transforms.py +++ b/tests/_utils/test_transforms.py @@ -1,9 +1,10 @@ import datetime as dt import pytest -from dagster_pyiceberg._utils import transforms from pyiceberg import transforms as iceberg_transforms +from dagster_pyiceberg._utils import transforms + @pytest.mark.parametrize( "start, end, expected_transformation", diff --git a/tests/dagster_pyiceberg/conftest.py b/tests/conftest.py similarity index 100% rename from tests/dagster_pyiceberg/conftest.py rename to tests/conftest.py diff --git a/tests/dagster_pyiceberg_pandas/conftest.py b/tests/dagster_pyiceberg_pandas/conftest.py deleted file mode 100644 index cbe60a1..0000000 --- a/tests/dagster_pyiceberg_pandas/conftest.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import Dict, Iterator - -import psycopg2 -import pytest -from pyiceberg.catalog.sql import SqlCatalog -from testcontainers.postgres import PostgresContainer - -postgres = PostgresContainer("postgres:17-alpine") - - -@pytest.fixture(scope="session", autouse=True) -def setup(request: pytest.FixtureRequest) -> Iterator[PostgresContainer]: - postgres.start() - - def remove_container(): - postgres.stop() - - request.addfinalizer(remove_container) - - yield postgres - - -@pytest.fixture(scope="session") -def postgres_connection( - setup: PostgresContainer, -) -> Iterator[psycopg2.extensions.connection]: - conn = psycopg2.connect( - database=setup.dbname, - port=setup.get_exposed_port(5432), - host=setup.get_container_host_ip(), - user=setup.username, - password=setup.password, - ) - yield conn - conn.close() - - -@pytest.fixture(scope="session") -def postgres_uri(setup: PostgresContainer) -> str: - return setup.get_connection_url() - - -# NB: we truncate all iceberg tables before each test -# that way, we don't have to worry about side effects -@pytest.fixture(scope="function", autouse=True) -def clean_iceberg_tables(postgres_connection: psycopg2.extensions.connection): - with postgres_connection.cursor() as cur: - cur.execute( - "SELECT tablename FROM pg_catalog.pg_tables WHERE tablename LIKE 'iceberg%';" - ) - for tbl in cur.fetchall(): - cur.execute(f"TRUNCATE TABLE {tbl[0]};") - postgres_connection.commit() - - -# NB: recreated for every test -@pytest.fixture(scope="function", autouse=True) -def warehouse_path(tmp_path_factory: pytest.TempPathFactory) -> str: - dir_ = tmp_path_factory.mktemp("warehouse") - return str(dir_.resolve()) - - -@pytest.fixture(scope="function") -def catalog_config_properties(warehouse_path: str, postgres_uri: str) -> Dict[str, str]: - return { - "uri": postgres_uri, - "warehouse": f"file://{str(warehouse_path)}", - } - - -@pytest.fixture(scope="session") -def catalog_name() -> str: - return "default" - - -@pytest.fixture(scope="function") -def catalog(catalog_name: str, catalog_config_properties: Dict[str, str]) -> SqlCatalog: - return SqlCatalog( - catalog_name, - **catalog_config_properties, - ) - - -@pytest.fixture(scope="function", autouse=True) -def namespace(catalog: SqlCatalog) -> str: - catalog.create_namespace("pytest") - return "pytest" diff --git a/tests/dagster_pyiceberg_polars/conftest.py b/tests/dagster_pyiceberg_polars/conftest.py deleted file mode 100644 index cbe60a1..0000000 --- a/tests/dagster_pyiceberg_polars/conftest.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import Dict, Iterator - -import psycopg2 -import pytest -from pyiceberg.catalog.sql import SqlCatalog -from testcontainers.postgres import PostgresContainer - -postgres = PostgresContainer("postgres:17-alpine") - - -@pytest.fixture(scope="session", autouse=True) -def setup(request: pytest.FixtureRequest) -> Iterator[PostgresContainer]: - postgres.start() - - def remove_container(): - postgres.stop() - - request.addfinalizer(remove_container) - - yield postgres - - -@pytest.fixture(scope="session") -def postgres_connection( - setup: PostgresContainer, -) -> Iterator[psycopg2.extensions.connection]: - conn = psycopg2.connect( - database=setup.dbname, - port=setup.get_exposed_port(5432), - host=setup.get_container_host_ip(), - user=setup.username, - password=setup.password, - ) - yield conn - conn.close() - - -@pytest.fixture(scope="session") -def postgres_uri(setup: PostgresContainer) -> str: - return setup.get_connection_url() - - -# NB: we truncate all iceberg tables before each test -# that way, we don't have to worry about side effects -@pytest.fixture(scope="function", autouse=True) -def clean_iceberg_tables(postgres_connection: psycopg2.extensions.connection): - with postgres_connection.cursor() as cur: - cur.execute( - "SELECT tablename FROM pg_catalog.pg_tables WHERE tablename LIKE 'iceberg%';" - ) - for tbl in cur.fetchall(): - cur.execute(f"TRUNCATE TABLE {tbl[0]};") - postgres_connection.commit() - - -# NB: recreated for every test -@pytest.fixture(scope="function", autouse=True) -def warehouse_path(tmp_path_factory: pytest.TempPathFactory) -> str: - dir_ = tmp_path_factory.mktemp("warehouse") - return str(dir_.resolve()) - - -@pytest.fixture(scope="function") -def catalog_config_properties(warehouse_path: str, postgres_uri: str) -> Dict[str, str]: - return { - "uri": postgres_uri, - "warehouse": f"file://{str(warehouse_path)}", - } - - -@pytest.fixture(scope="session") -def catalog_name() -> str: - return "default" - - -@pytest.fixture(scope="function") -def catalog(catalog_name: str, catalog_config_properties: Dict[str, str]) -> SqlCatalog: - return SqlCatalog( - catalog_name, - **catalog_config_properties, - ) - - -@pytest.fixture(scope="function", autouse=True) -def namespace(catalog: SqlCatalog) -> str: - catalog.create_namespace("pytest") - return "pytest" diff --git a/tests/dagster_pyiceberg/test_io_manager.py b/tests/io_managers/test_arrow_io_manager.py similarity index 99% rename from tests/dagster_pyiceberg/test_io_manager.py rename to tests/io_managers/test_arrow_io_manager.py index 0c39374..11fab19 100644 --- a/tests/dagster_pyiceberg/test_io_manager.py +++ b/tests/io_managers/test_arrow_io_manager.py @@ -12,9 +12,10 @@ asset, materialize, ) -from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig from pyiceberg.catalog.sql import SqlCatalog +from dagster_pyiceberg import IcebergPyarrowIOManager, IcebergSqlCatalogConfig + @pytest.fixture def io_manager( diff --git a/tests/dagster_pyiceberg_pandas/test_pyiceberg_pandas_type_handler.py b/tests/io_managers/test_daft_io_manager.py similarity index 77% rename from tests/dagster_pyiceberg_pandas/test_pyiceberg_pandas_type_handler.py rename to tests/io_managers/test_daft_io_manager.py index 2919368..46eb36d 100644 --- a/tests/dagster_pyiceberg_pandas/test_pyiceberg_pandas_type_handler.py +++ b/tests/io_managers/test_daft_io_manager.py @@ -1,8 +1,7 @@ import datetime as dt from typing import Dict -import pandas as pd -import pyarrow as pa +import daft as da import pytest from dagster import ( AssetExecutionContext, @@ -13,16 +12,16 @@ asset, materialize, ) -from dagster_pyiceberg import IcebergSqlCatalogConfig -from dagster_pyiceberg_pandas import IcebergPandasIOManager from pyiceberg.catalog.sql import SqlCatalog +from dagster_pyiceberg import IcebergDaftIOManager, IcebergSqlCatalogConfig -@pytest.fixture(scope="function") + +@pytest.fixture def io_manager( catalog_name: str, namespace: str, catalog_config_properties: Dict[str, str] -) -> IcebergPandasIOManager: - return IcebergPandasIOManager( +) -> IcebergDaftIOManager: + return IcebergDaftIOManager( name=catalog_name, config=IcebergSqlCatalogConfig(properties=catalog_config_properties), schema=namespace, @@ -30,6 +29,12 @@ def io_manager( ) +@pytest.fixture +def custom_db_io_manager(io_manager: IcebergDaftIOManager): + return io_manager.create_io_manager(None) + + +# NB: iceberg table identifiers are namespace + asset names (see below) @pytest.fixture(scope="function") def asset_b_df_table_identifier(namespace: str) -> str: return f"{namespace}.b_df" @@ -56,13 +61,13 @@ def asset_multi_partitioned_table_identifier(namespace: str) -> str: @asset(key_prefix=["my_schema"]) -def b_df() -> pd.DataFrame: - return pd.DataFrame.from_dict({"a": [1, 2, 3], "b": [4, 5, 6]}) +def b_df() -> da.DataFrame: + return da.from_pydict({"a": [1, 2, 3], "b": [4, 5, 6]}) @asset(key_prefix=["my_schema"]) -def b_plus_one(b_df: pd.DataFrame) -> pd.DataFrame: - return b_df.assign(a=lambda _: [2, 3, 4]) +def b_plus_one(b_df: da.DataFrame) -> da.DataFrame: + return b_df.with_column("a", da.col("a") + 1) @asset( @@ -71,11 +76,11 @@ def b_plus_one(b_df: pd.DataFrame) -> pd.DataFrame: config_schema={"value": str}, metadata={"partition_expr": "partition"}, ) -def hourly_partitioned(context: AssetExecutionContext) -> pa.Table: +def hourly_partitioned(context: AssetExecutionContext) -> da.DataFrame: partition = dt.datetime.strptime(context.partition_key, "%Y-%m-%d-%H:%M") value = context.op_execution_context.op_config["value"] - return pa.Table.from_pydict({"partition": [partition], "value": [value], "b": [1]}) + return da.from_pydict({"partition": [partition], "value": [value], "b": [1]}) @asset( @@ -84,45 +89,52 @@ def hourly_partitioned(context: AssetExecutionContext) -> pa.Table: config_schema={"value": str}, metadata={"partition_expr": "partition"}, ) -def daily_partitioned(context: AssetExecutionContext) -> pa.Table: +def daily_partitioned(context: AssetExecutionContext) -> da.DataFrame: partition = dt.datetime.strptime(context.partition_key, "%Y-%m-%d").date() value = context.op_execution_context.op_config["value"] - return pa.Table.from_pydict({"partition": [partition], "value": [value], "b": [1]}) + return da.from_pydict({"partition": [partition], "value": [value], "b": [1]}) @asset( key_prefix=["my_schema"], partitions_def=MultiPartitionsDefinition( partitions_defs={ - "date": DailyPartitionsDefinition(start_date="2022-01-01"), + "date": DailyPartitionsDefinition( + start_date="2022-01-01", end_date="2022-01-10" + ), "category": StaticPartitionsDefinition(["a", "b", "c"]), } ), config_schema={"value": str}, metadata={ "partition_expr": { - "date": "date", - "category": "category", + "date": "date_this", + "category": "category_this", } }, ) -def multi_partitioned(context: AssetExecutionContext) -> pa.Table: +def multi_partitioned(context: AssetExecutionContext) -> da.DataFrame: category, date = context.partition_key.split("|") date_parsed = dt.datetime.strptime(date, "%Y-%m-%d").date() value = context.op_execution_context.op_config["value"] - return pa.Table.from_pydict( - {"date": [date_parsed], "value": [value], "b": [1], "category": [category]} + return da.from_pydict( + { + "date_this": [date_parsed], + "value": [value], + "b": [1], + "category_this": [category], + } ) -def test_iceberg_pandas_io_manager_with_assets( +def test_iceberg_io_manager_with_assets( asset_b_df_table_identifier: str, asset_b_plus_one_table_identifier: str, catalog: SqlCatalog, - io_manager: IcebergPandasIOManager, + io_manager: IcebergDaftIOManager, ): resource_defs = {"io_manager": io_manager} @@ -142,7 +154,7 @@ def test_iceberg_pandas_io_manager_with_assets( def test_iceberg_io_manager_with_daily_partitioned_assets( asset_daily_partitioned_table_identifier: str, catalog: SqlCatalog, - io_manager: IcebergPandasIOManager, + io_manager: IcebergDaftIOManager, ): resource_defs = {"io_manager": io_manager} @@ -172,7 +184,7 @@ def test_iceberg_io_manager_with_daily_partitioned_assets( def test_iceberg_io_manager_with_hourly_partitioned_assets( asset_hourly_partitioned_table_identifier: str, catalog: SqlCatalog, - io_manager: IcebergPandasIOManager, + io_manager: IcebergDaftIOManager, ): resource_defs = {"io_manager": io_manager} @@ -202,7 +214,7 @@ def test_iceberg_io_manager_with_hourly_partitioned_assets( def test_iceberg_io_manager_with_multipartitioned_assets( asset_multi_partitioned_table_identifier: str, catalog: SqlCatalog, - io_manager: IcebergPandasIOManager, + io_manager: IcebergDaftIOManager, ): resource_defs = {"io_manager": io_manager} @@ -226,10 +238,10 @@ def test_iceberg_io_manager_with_multipartitioned_assets( table = catalog.load_table(asset_multi_partitioned_table_identifier) assert len(table.spec().fields) == 2 - assert [f.name for f in table.spec().fields] == ["category", "date"] + assert [f.name for f in table.spec().fields] == ["category_this", "date_this"] out_df = table.scan().to_arrow() - assert out_df["date"].to_pylist() == [ + assert out_df["date_this"].to_pylist() == [ dt.date(2022, 1, 2), dt.date(2022, 1, 2), dt.date(2022, 1, 2), @@ -237,4 +249,4 @@ def test_iceberg_io_manager_with_multipartitioned_assets( dt.date(2022, 1, 1), dt.date(2022, 1, 1), ] - assert out_df["category"].to_pylist() == ["c", "b", "a", "c", "b", "a"] + assert out_df["category_this"].to_pylist() == ["c", "b", "a", "c", "b", "a"] diff --git a/tests/dagster_pyiceberg_polars/test_pyiceberg_polars_type_handler.py b/tests/io_managers/test_polars_io_manager.py similarity index 67% rename from tests/dagster_pyiceberg_polars/test_pyiceberg_polars_type_handler.py rename to tests/io_managers/test_polars_io_manager.py index 12805c6..613f1fe 100644 --- a/tests/dagster_pyiceberg_polars/test_pyiceberg_polars_type_handler.py +++ b/tests/io_managers/test_polars_io_manager.py @@ -12,12 +12,12 @@ asset, materialize, ) -from dagster_pyiceberg import IcebergSqlCatalogConfig -from dagster_pyiceberg_polars import IcebergPolarsIOManager from pyiceberg.catalog.sql import SqlCatalog +from dagster_pyiceberg import IcebergPolarsIOManager, IcebergSqlCatalogConfig -@pytest.fixture(scope="function") + +@pytest.fixture def io_manager( catalog_name: str, namespace: str, catalog_config_properties: Dict[str, str] ) -> IcebergPolarsIOManager: @@ -29,6 +29,12 @@ def io_manager( ) +@pytest.fixture +def custom_db_io_manager(io_manager: IcebergPolarsIOManager): + return io_manager.create_io_manager(None) + + +# NB: iceberg table identifiers are namespace + asset names (see below) @pytest.fixture(scope="function") def asset_b_df_table_identifier(namespace: str) -> str: return f"{namespace}.b_df" @@ -55,13 +61,14 @@ def asset_multi_partitioned_table_identifier(namespace: str) -> str: @asset(key_prefix=["my_schema"]) -def b_df() -> pl.DataFrame: - return pl.from_dict({"a": [1, 2, 3], "b": [4, 5, 6]}) +def b_df() -> pl.LazyFrame: + pdf = pl.from_dict({"a": [1, 2, 3], "b": [4, 5, 6]}) + return pdf.lazy() @asset(key_prefix=["my_schema"]) -def b_plus_one(b_df: pl.DataFrame) -> pl.DataFrame: - return b_df.with_columns(a=pl.Series([2, 3, 4])) +def b_plus_one(b_df: pl.LazyFrame) -> pl.LazyFrame: + return b_df.with_columns(a=pl.col("a") + 1) @asset( @@ -77,19 +84,6 @@ def hourly_partitioned(context: AssetExecutionContext) -> pl.DataFrame: return pl.from_dict({"partition": [partition], "value": [value], "b": [1]}) -@asset( - key_prefix=["my_schema"], - partitions_def=HourlyPartitionsDefinition(start_date=dt.datetime(2022, 1, 1, 0)), - config_schema={"value": str}, - metadata={"partition_expr": "partition"}, -) -def hourly_partitioned_lazy(context: AssetExecutionContext) -> pl.LazyFrame: - partition = dt.datetime.strptime(context.partition_key, "%Y-%m-%d-%H:%M") - value = context.op_execution_context.op_config["value"] - - return pl.from_dict({"partition": [partition], "value": [value], "b": [1]}).lazy() - - @asset( key_prefix=["my_schema"], partitions_def=DailyPartitionsDefinition(start_date="2022-01-01"), @@ -103,32 +97,21 @@ def daily_partitioned(context: AssetExecutionContext) -> pl.DataFrame: return pl.from_dict({"partition": [partition], "value": [value], "b": [1]}) -@asset( - key_prefix=["my_schema"], - partitions_def=DailyPartitionsDefinition(start_date="2022-01-01"), - config_schema={"value": str}, - metadata={"partition_expr": "partition"}, -) -def daily_partitioned_lazy(context: AssetExecutionContext) -> pl.LazyFrame: - partition = dt.datetime.strptime(context.partition_key, "%Y-%m-%d").date() - value = context.op_execution_context.op_config["value"] - - return pl.from_dict({"partition": [partition], "value": [value], "b": [1]}).lazy() - - @asset( key_prefix=["my_schema"], partitions_def=MultiPartitionsDefinition( partitions_defs={ - "date": DailyPartitionsDefinition(start_date="2022-01-01"), + "date": DailyPartitionsDefinition( + start_date="2022-01-01", end_date="2022-01-10" + ), "category": StaticPartitionsDefinition(["a", "b", "c"]), } ), config_schema={"value": str}, metadata={ "partition_expr": { - "date": "date", - "category": "category", + "date": "date_this", + "category": "category_this", } }, ) @@ -139,11 +122,16 @@ def multi_partitioned(context: AssetExecutionContext) -> pl.DataFrame: value = context.op_execution_context.op_config["value"] return pl.from_dict( - {"date": [date_parsed], "value": [value], "b": [1], "category": [category]} + { + "date_this": [date_parsed], + "value": [value], + "b": [1], + "category_this": [category], + } ) -def test_iceberg_pandas_io_manager_with_assets( +def test_iceberg_io_manager_with_assets( asset_b_df_table_identifier: str, asset_b_plus_one_table_identifier: str, catalog: SqlCatalog, @@ -164,9 +152,7 @@ def test_iceberg_pandas_io_manager_with_assets( assert out_dt["a"].to_pylist() == [2, 3, 4] -@pytest.mark.parametrize("lazy", [False, True]) def test_iceberg_io_manager_with_daily_partitioned_assets( - lazy: bool, asset_daily_partitioned_table_identifier: str, catalog: SqlCatalog, io_manager: IcebergPolarsIOManager, @@ -175,26 +161,16 @@ def test_iceberg_io_manager_with_daily_partitioned_assets( for date in ["2022-01-01", "2022-01-02", "2022-01-03"]: res = materialize( - [daily_partitioned_lazy if lazy else daily_partitioned], + [daily_partitioned], partition_key=date, resources=resource_defs, run_config={ - "ops": { - ( - "my_schema__daily_partitioned_lazy" - if lazy - else "my_schema__daily_partitioned" - ): {"config": {"value": "1"}} - } + "ops": {"my_schema__daily_partitioned": {"config": {"value": "1"}}} }, ) assert res.success - table = catalog.load_table( - f"{asset_daily_partitioned_table_identifier}_lazy" - if lazy - else asset_daily_partitioned_table_identifier - ) + table = catalog.load_table(asset_daily_partitioned_table_identifier) assert len(table.spec().fields) == 1 assert table.spec().fields[0].name == "partition" @@ -206,9 +182,7 @@ def test_iceberg_io_manager_with_daily_partitioned_assets( ] -@pytest.mark.parametrize("lazy", [False, True]) def test_iceberg_io_manager_with_hourly_partitioned_assets( - lazy: bool, asset_hourly_partitioned_table_identifier: str, catalog: SqlCatalog, io_manager: IcebergPolarsIOManager, @@ -217,26 +191,16 @@ def test_iceberg_io_manager_with_hourly_partitioned_assets( for date in ["2022-01-01-01:00", "2022-01-01-02:00", "2022-01-01-03:00"]: res = materialize( - [hourly_partitioned_lazy if lazy else hourly_partitioned], + [hourly_partitioned], partition_key=date, resources=resource_defs, run_config={ - "ops": { - ( - "my_schema__hourly_partitioned_lazy" - if lazy - else "my_schema__hourly_partitioned" - ): {"config": {"value": "1"}} - } + "ops": {"my_schema__hourly_partitioned": {"config": {"value": "1"}}} }, ) assert res.success - table = catalog.load_table( - f"{asset_hourly_partitioned_table_identifier}_lazy" - if lazy - else asset_hourly_partitioned_table_identifier - ) + table = catalog.load_table(asset_hourly_partitioned_table_identifier) assert len(table.spec().fields) == 1 assert table.spec().fields[0].name == "partition" @@ -275,10 +239,10 @@ def test_iceberg_io_manager_with_multipartitioned_assets( table = catalog.load_table(asset_multi_partitioned_table_identifier) assert len(table.spec().fields) == 2 - assert [f.name for f in table.spec().fields] == ["category", "date"] + assert [f.name for f in table.spec().fields] == ["category_this", "date_this"] out_df = table.scan().to_arrow() - assert out_df["date"].to_pylist() == [ + assert out_df["date_this"].to_pylist() == [ dt.date(2022, 1, 2), dt.date(2022, 1, 2), dt.date(2022, 1, 2), @@ -286,4 +250,4 @@ def test_iceberg_io_manager_with_multipartitioned_assets( dt.date(2022, 1, 1), dt.date(2022, 1, 1), ] - assert out_df["category"].to_pylist() == ["c", "b", "a", "c", "b", "a"] + assert out_df["category_this"].to_pylist() == ["c", "b", "a", "c", "b", "a"] diff --git a/tests/dagster_pyiceberg/test_resource.py b/tests/test_resource.py similarity index 99% rename from tests/dagster_pyiceberg/test_resource.py rename to tests/test_resource.py index 5cb7ca0..04ecf66 100644 --- a/tests/dagster_pyiceberg/test_resource.py +++ b/tests/test_resource.py @@ -3,10 +3,11 @@ import pyarrow as pa import pytest from dagster import asset, materialize -from dagster_pyiceberg import IcebergSqlCatalogConfig, IcebergTableResource from pyiceberg.catalog.sql import SqlCatalog from pyiceberg.table import Table +from dagster_pyiceberg import IcebergSqlCatalogConfig, IcebergTableResource + @pytest.fixture(scope="module") def table_name() -> str: diff --git a/uv.lock b/uv.lock index 3bb6e0f..7afa74f 100644 --- a/uv.lock +++ b/uv.lock @@ -7,14 +7,6 @@ resolution-markers = [ "python_full_version >= '3.13'", ] -[manifest] -members = [ - "dagster-pyiceberg", - "dagster-pyiceberg-pandas", - "dagster-pyiceberg-polars", - "dagster-pyiceberg-project", -] - [[package]] name = "alembic" version = "1.14.0" @@ -301,61 +293,61 @@ wheels = [ [[package]] name = "coverage" -version = "7.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/93/4ad92f71e28ece5c0326e5f4a6630aa4928a8846654a65cfff69b49b95b9/coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", size = 206713 }, - { url = "https://files.pythonhosted.org/packages/01/ae/747a580b1eda3f2e431d87de48f0604bd7bc92e52a1a95185a4aa585bc47/coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", size = 207149 }, - { url = "https://files.pythonhosted.org/packages/07/1a/1f573f8a6145f6d4c9130bbc120e0024daf1b24cf2a78d7393fa6eb6aba7/coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", size = 235584 }, - { url = "https://files.pythonhosted.org/packages/40/42/c8523f2e4db34aa9389caee0d3688b6ada7a84fcc782e943a868a7f302bd/coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/8d/95/565c310fffa16ede1a042e9ea1ca3962af0d8eb5543bc72df6b91dc0c3d5/coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", size = 234649 }, - { url = "https://files.pythonhosted.org/packages/d5/81/3b550674d98968ec29c92e3e8650682be6c8b1fa7581a059e7e12e74c431/coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", size = 233744 }, - { url = "https://files.pythonhosted.org/packages/0d/70/d66c7f51b3e33aabc5ea9f9624c1c9d9655472962270eb5e7b0d32707224/coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", size = 232204 }, - { url = "https://files.pythonhosted.org/packages/23/2d/2b3a2dbed7a5f40693404c8a09e779d7c1a5fbed089d3e7224c002129ec8/coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", size = 233335 }, - { url = "https://files.pythonhosted.org/packages/5a/4f/92d1d2ad720d698a4e71c176eacf531bfb8e0721d5ad560556f2c484a513/coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", size = 209435 }, - { url = "https://files.pythonhosted.org/packages/c7/b9/cdf158e7991e2287bcf9082670928badb73d310047facac203ff8dcd5ff3/coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", size = 210243 }, - { url = "https://files.pythonhosted.org/packages/87/31/9c0cf84f0dfcbe4215b7eb95c31777cdc0483c13390e69584c8150c85175/coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", size = 206819 }, - { url = "https://files.pythonhosted.org/packages/53/ed/a38401079ad320ad6e054a01ec2b61d270511aeb3c201c80e99c841229d5/coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", size = 207263 }, - { url = "https://files.pythonhosted.org/packages/20/e7/c3ad33b179ab4213f0d70da25a9c214d52464efa11caeab438592eb1d837/coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", size = 239205 }, - { url = "https://files.pythonhosted.org/packages/36/91/fc02e8d8e694f557752120487fd982f654ba1421bbaa5560debf96ddceda/coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", size = 236612 }, - { url = "https://files.pythonhosted.org/packages/cc/57/cb08f0eda0389a9a8aaa4fc1f9fec7ac361c3e2d68efd5890d7042c18aa3/coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", size = 238479 }, - { url = "https://files.pythonhosted.org/packages/d5/c9/2c7681a9b3ca6e6f43d489c2e6653a53278ed857fd6e7010490c307b0a47/coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", size = 237405 }, - { url = "https://files.pythonhosted.org/packages/b5/4e/ebfc6944b96317df8b537ae875d2e57c27b84eb98820bc0a1055f358f056/coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", size = 236038 }, - { url = "https://files.pythonhosted.org/packages/13/f2/3a0bf1841a97c0654905e2ef531170f02c89fad2555879db8fe41a097871/coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", size = 236812 }, - { url = "https://files.pythonhosted.org/packages/b9/9c/66bf59226b52ce6ed9541b02d33e80a6e816a832558fbdc1111a7bd3abd4/coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", size = 209400 }, - { url = "https://files.pythonhosted.org/packages/2a/a0/b0790934c04dfc8d658d4a62acb8f7ca0efdf3818456fcad757b11c6479d/coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", size = 210243 }, - { url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013 }, - { url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251 }, - { url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268 }, - { url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298 }, - { url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367 }, - { url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853 }, - { url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160 }, - { url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824 }, - { url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639 }, - { url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428 }, - { url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039 }, - { url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298 }, - { url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813 }, - { url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959 }, - { url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950 }, - { url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610 }, - { url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697 }, - { url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541 }, - { url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707 }, - { url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439 }, - { url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784 }, - { url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058 }, - { url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772 }, - { url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490 }, - { url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848 }, - { url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340 }, - { url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229 }, - { url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510 }, - { url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353 }, - { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 }, - { url = "https://files.pythonhosted.org/packages/cc/56/e1d75e8981a2a92c2a777e67c26efa96c66da59d645423146eb9ff3a851b/coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", size = 198954 }, +version = "7.6.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/68/26895f8b068e384b1ec9ab122565b913b735e6b4c618b3d265a280607edc/coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24", size = 799938 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/c9/84898713e61208ddbe71b991d8f311d9ca175629ce5f1a46018acc643572/coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e", size = 206875 }, + { url = "https://files.pythonhosted.org/packages/f0/69/7dfd65f0e284617f72d974f6dfedc7bc16f86172e5bc6ebc8b63430263f3/coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45", size = 207307 }, + { url = "https://files.pythonhosted.org/packages/d1/ce/6e356b2bc751bdaadd77c714336b98ec45ccaf0cfe085b6b25d34f7cceb8/coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1", size = 235744 }, + { url = "https://files.pythonhosted.org/packages/35/49/a7ab3d5a507d32344994cab856784e8d603c0b698070f7667c3ae41e8e50/coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c", size = 233645 }, + { url = "https://files.pythonhosted.org/packages/bd/41/de07328d2e79916fcc6cd53a5a1d18d163483519ab95f7f60fe15276811c/coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2", size = 234807 }, + { url = "https://files.pythonhosted.org/packages/e4/cc/2a669319b1295e0c52e8cfbbb163b32188b62f3b0bbe7014ef402b24b7cf/coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06", size = 233902 }, + { url = "https://files.pythonhosted.org/packages/68/71/a1bb90cb177358a2d364b3968a2069225f614d6824c3d959dee688ca0902/coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777", size = 232363 }, + { url = "https://files.pythonhosted.org/packages/eb/dc/87551219d3437214523d1c7de0a717bead7a3369ed9bae05a7fd2854476f/coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314", size = 233493 }, + { url = "https://files.pythonhosted.org/packages/ca/a4/d74ae3a3fb9e55fe5d9b811ce68a6bd8df3ae0a92c336acbc00075bc24fa/coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a", size = 209593 }, + { url = "https://files.pythonhosted.org/packages/77/cb/7984c4d0404e8fcc4ada226b240965ef056e7a20e61a18c9038bf88e7624/coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163", size = 210398 }, + { url = "https://files.pythonhosted.org/packages/c6/d7/1bf7bb0943237149ad01977190ac5c2e17add1f4fe7cabc06401682137f6/coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469", size = 206979 }, + { url = "https://files.pythonhosted.org/packages/83/eb/863b2cd654353b94c6ad834008df813424bf3e8f110e5f655fe5dc4c423b/coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99", size = 207431 }, + { url = "https://files.pythonhosted.org/packages/35/c9/d7a02a9654c41174fb99402c0fbd9583d0d2cb8714e7f948117fa7f919c4/coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec", size = 239368 }, + { url = "https://files.pythonhosted.org/packages/11/64/6c43a0ec43e5ddc5e09b0b589e3fd31def05fc463920d084e5af35fe527d/coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b", size = 236769 }, + { url = "https://files.pythonhosted.org/packages/1c/dc/e77d98ae433c556c29328712a07fed0e6d159a63b2ec81039ce0a13a24a3/coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a", size = 238634 }, + { url = "https://files.pythonhosted.org/packages/cc/84/50df3a8426d686057496171b4ccdb64528dacc4f42e94dceb7de3c598a69/coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b", size = 237562 }, + { url = "https://files.pythonhosted.org/packages/2e/0f/9560196247574c1ccdab64cb923d69119fd5abd5b3db28d601ab2b452861/coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d", size = 236197 }, + { url = "https://files.pythonhosted.org/packages/df/14/38b7c081e86e845df1867143ddb6e05bf8395f60ab3923c023a56d97cca1/coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4", size = 236970 }, + { url = "https://files.pythonhosted.org/packages/8b/f3/af34f814ca3814f798878ae368b638edb91298595470614f5265f3f416fa/coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2", size = 209557 }, + { url = "https://files.pythonhosted.org/packages/5a/9e/5d1080d83d752873bd9dedea5524c0f5fe68a3d5e1e58c590865bd724591/coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f", size = 210402 }, + { url = "https://files.pythonhosted.org/packages/84/30/30e9df650b9038962c62d900b093a17414d5b43b4d07d47b8698d9e7ce26/coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9", size = 207172 }, + { url = "https://files.pythonhosted.org/packages/88/8b/e28f86412317b9514692fd6f9d8ac6faa12494c3f470c3c63f202e10c756/coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b", size = 207406 }, + { url = "https://files.pythonhosted.org/packages/ac/46/da1bd9a3a893f74f5ce85f35e2755fcb00a80ed21e18d300c54f64938b1c/coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c", size = 240424 }, + { url = "https://files.pythonhosted.org/packages/f6/12/af8e932496de1997bf4a36785d025ddac6427cbaf6954f26c2edaf21a58a/coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1", size = 237456 }, + { url = "https://files.pythonhosted.org/packages/60/a2/23eb11eb60f825a84397cb94701d6f41d2e8e88ad7d0ba2b4339f38435fb/coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354", size = 239527 }, + { url = "https://files.pythonhosted.org/packages/47/9e/63b318bc469308a32b0fbd6c80e2ea05dd7a2b7e840a46b3974843083a8c/coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433", size = 239011 }, + { url = "https://files.pythonhosted.org/packages/99/47/1e84b067df3f021dfbc9cba09ec9acd4cb64938648a234e5bdf3006fd08b/coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f", size = 237316 }, + { url = "https://files.pythonhosted.org/packages/12/9d/96baaafc948d4a0ef2248a611d41051eea0917ef881d744879dd20be7c4a/coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb", size = 238980 }, + { url = "https://files.pythonhosted.org/packages/87/d9/97af1886ca3f612d0cea2999d33e90d2f5b8fdf9bedc2d3bc75883efec4c/coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76", size = 209801 }, + { url = "https://files.pythonhosted.org/packages/f8/4d/1e31c2018b1b3738154639f94188b1f54098fbf0f80c7ec104928576d0bb/coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c", size = 210587 }, + { url = "https://files.pythonhosted.org/packages/21/87/c590d0c7eeb884995d9d06b429c5e88e9fcd65d3a6a686d9476cb50b72a9/coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3", size = 207199 }, + { url = "https://files.pythonhosted.org/packages/40/ee/c88473c4f69c952f4425fabe045cb78d2027634ce50c9d7f7987d389b604/coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab", size = 207454 }, + { url = "https://files.pythonhosted.org/packages/b8/07/afda6e10c50e3a8c21020c5c1d1b4f3d7eff1c190305cef2962adf8de018/coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808", size = 239971 }, + { url = "https://files.pythonhosted.org/packages/85/43/bd1934b75e31f2a49665be6a6b7f8bfaff7266ba19721bdb90239f5e9ed7/coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc", size = 237119 }, + { url = "https://files.pythonhosted.org/packages/2b/19/7a70458c1624724086195b40628e91bc5b9ca180cdfefcc778285c49c7b2/coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8", size = 239109 }, + { url = "https://files.pythonhosted.org/packages/f3/2c/3dee671415ff13c05ca68243b2264fc95a5eea57697cffa7986b68b8f608/coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a", size = 238769 }, + { url = "https://files.pythonhosted.org/packages/37/ad/e0d1228638711aeacacc98d1197af6226b6d062d12c81a6bcc17d3234533/coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55", size = 236854 }, + { url = "https://files.pythonhosted.org/packages/90/95/6467e9d9765a63c7f142703a7f212f6af114bd73a6c1cffeb7ad7f003a86/coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384", size = 238701 }, + { url = "https://files.pythonhosted.org/packages/b2/7a/fc11a163f0fd6ce8539d0f1b565873fe6903b900214ff71b5d80d16154c3/coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30", size = 209865 }, + { url = "https://files.pythonhosted.org/packages/f2/91/58be3a56efff0c3481e48e2caa56d5d6f3c5c8d385bf4adbecdfd85484b0/coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42", size = 210597 }, + { url = "https://files.pythonhosted.org/packages/34/7e/fed983809c2eccb09c5ddccfdb08efb7f2dd1ae3454dabf1c92c5a2e9946/coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413", size = 207944 }, + { url = "https://files.pythonhosted.org/packages/c7/e0/2c1a157986a3927c3920e8e3938a3fdf33ea22b6f371dc3b679f13f619e2/coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd", size = 208215 }, + { url = "https://files.pythonhosted.org/packages/35/2f/77b086b228f6443ae5499467d1629c7428925b390cd171350c403bc00f14/coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37", size = 250930 }, + { url = "https://files.pythonhosted.org/packages/60/d8/2ffea937d89ee328fc6e47c2515b890735bdf3f195d507d1c78b5fa96939/coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b", size = 246647 }, + { url = "https://files.pythonhosted.org/packages/b2/81/efbb3b00a7f7eb5f54a3b3b9f19b26d770a0b7d3870d651f07d2451c5504/coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d", size = 249006 }, + { url = "https://files.pythonhosted.org/packages/eb/91/ce36990cbefaf7909e96c888ed4d83f3471fc1be3273a5beda10896cde0f/coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529", size = 248500 }, + { url = "https://files.pythonhosted.org/packages/75/3f/b8c87dfdd96276870fb4abc7e2957cba7d20d8a435fcd816d807869ec833/coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b", size = 246388 }, + { url = "https://files.pythonhosted.org/packages/a0/51/62273e1d5c25bb8fbef5fbbadc75b4a3e08c11b80516d0a97c25e5cced5b/coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3", size = 247669 }, + { url = "https://files.pythonhosted.org/packages/75/e5/d7772e56a7eace80e98ac39f2756d4b690fc0ce2384418174e02519a26a8/coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8", size = 210510 }, + { url = "https://files.pythonhosted.org/packages/2d/12/f2666e4e36b43221391ffcd971ab0c50e19439c521c2c87cd7e0b49ddba2/coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56", size = 211660 }, + { url = "https://files.pythonhosted.org/packages/e1/ec/dc663f7d34651aca74a531d10800595d9ec28a78b8306705721900b17a23/coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671", size = 199113 }, ] [package.optional-dependencies] @@ -378,19 +370,19 @@ wheels = [ [[package]] name = "dagit" -version = "1.9.1" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dagster-webserver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/df/0644bb7d4bee5358edcff6d3a7a7d8b3d84474aa5292b03489fd6490fc9b/dagit-1.9.1.tar.gz", hash = "sha256:43603f27af00c966c29815ed156e85c18b2bc1e1a31ba5b6b5d19ae6e8c721c4", size = 6188 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/38/38c140bdc66b3ee001416d645e94e7e54e04a3ab547347884661ef5c77f6/dagit-1.9.2.tar.gz", hash = "sha256:8f82a5ba39e4e3ebfc305069a643ae4fe87ca910b2b4a51530fcc734da54bc2f", size = 6174 } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/3b/c97926102a3bf12567877b23798c58207554e4d2896966bcb219519bd2b9/dagit-1.9.1-py3-none-any.whl", hash = "sha256:477bbcde2488a2cc90527a79199641b2ebf9e47286fdc0bffd3ab4d987338610", size = 6196 }, + { url = "https://files.pythonhosted.org/packages/48/ff/f02afc8a3ec3c67e0f8098b6ae6a471e0b5f26d45449a25c9dfde4a354b6/dagit-1.9.2-py3-none-any.whl", hash = "sha256:b3fec1adaa826b96cd214674f7a47a225563a30ca3eb2a599dedadd6e97a963f", size = 6199 }, ] [[package]] name = "dagster" -version = "1.9.1" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -425,14 +417,14 @@ dependencies = [ { name = "universal-pathlib" }, { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/c7/da8b6faa0dce8276c2f355fb791d61dca01e1129731f84026101607b5877/dagster-1.9.1.tar.gz", hash = "sha256:a93a7996c5d5ad779f0ccf277ff4059eb60281947247fc8faafb18916166b654", size = 1372375 } +sdist = { url = "https://files.pythonhosted.org/packages/7a/0e/40464db6918a49551aa834a6582bf8fc8a67f7889373312965e5caf4597a/dagster-1.9.2.tar.gz", hash = "sha256:fdb98dabd953586c295e315541b690802934150b6638006fa8cef9d7cb2e44f4", size = 1375037 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/70/43089060d64ada009c9b299eb53f300beddfee3d0d126ec2f35f323aaf07/dagster-1.9.1-py3-none-any.whl", hash = "sha256:f60afe7a2f8134460377aa0112648273c7a61f3fdd0aa6ae57ebaa5d05e71de0", size = 1679796 }, + { url = "https://files.pythonhosted.org/packages/38/50/31bda548307a4ccd337e24462a81130f2bf35fa0e19a9deb67112c3cc4ce/dagster-1.9.2-py3-none-any.whl", hash = "sha256:fb6732882469e32168ebaf47f014e9007ba3c6d41350f0cf43c9b943dedb56f7", size = 1682719 }, ] [[package]] name = "dagster-graphql" -version = "1.9.1" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dagster" }, @@ -441,24 +433,24 @@ dependencies = [ { name = "requests" }, { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/04/d1faf3c6a10e58bda2adbb97218cd26db18541434deaff2655772f9c0ec1/dagster-graphql-1.9.1.tar.gz", hash = "sha256:05ffa05a3e74d779d0931b89ff2db793ace9a192cd4efe80988b36112633f943", size = 145495 } +sdist = { url = "https://files.pythonhosted.org/packages/03/24/03aaa6b53be6eacae45cd4c0335ab6338f44554f47befd5432d5ebe6e131/dagster-graphql-1.9.2.tar.gz", hash = "sha256:23d824ca8cdf411607dedbad68e81e19a28cb498cbc1ea8ec9ee48c5fbd479e1", size = 145662 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/b3/78d146ac38a6c008e1b5a36718451d615c1cab647a1b23a1e01d0c8c3586/dagster_graphql-1.9.1-py3-none-any.whl", hash = "sha256:ea534db90ea5554eab868861bb191a8e11938735a5bfa0bdd2f07d0071987d9e", size = 190899 }, + { url = "https://files.pythonhosted.org/packages/67/ce/e8e3b3b222685bc036c60fb3f22f019c003375f530f7ba02d0f1002d4a27/dagster_graphql-1.9.2-py3-none-any.whl", hash = "sha256:22388d13c257166759ef161b8e15f65a101a8a2f59c606b056703418349af5fa", size = 191057 }, ] [[package]] name = "dagster-pipes" -version = "1.9.1" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/ee/a2ec1ee4bdd8d485f328daf05b267b45ed8b1b15dfca9a2f349b0ed28ab0/dagster-pipes-1.9.1.tar.gz", hash = "sha256:847991e615e2f98326afda6b4d6cecd2665a9afa803430bc10e90eba9123f124", size = 17277 } +sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/420f4111d993564484abad776a9edb6a0398decb0a5e01d7e875cd8d52e1/dagster-pipes-1.9.2.tar.gz", hash = "sha256:63c64123b3826c099d2b9fe09604c3d755fcfaeaf08d0920628c54cba5123e47", size = 17725 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/a0/2d3a6e4a195ca7bb3718ae1ea959ee52f8009769640b2786b4525f23f592/dagster_pipes-1.9.1-py3-none-any.whl", hash = "sha256:78e8a2227522a9f3b9f773b58c8dc7329e08151db3c622d975e3977c043331a9", size = 17099 }, + { url = "https://files.pythonhosted.org/packages/f0/ba/46ad619c4cb985b600575d4321d3275963664a131e6570e351d5b8bfa4f9/dagster_pipes-1.9.2-py3-none-any.whl", hash = "sha256:3e18a82617e7287450d740bab0dbae4d8210c9e1b08c33cc395550eb58206007", size = 17549 }, ] [[package]] name = "dagster-pyiceberg" version = "0.0.0" -source = { editable = "packages/dagster_pyiceberg" } +source = { editable = "." } dependencies = [ { name = "dagster" }, { name = "pendulum" }, @@ -466,55 +458,17 @@ dependencies = [ { name = "tenacity" }, ] -[package.metadata] -requires-dist = [ - { name = "dagster", specifier = ">=1.8.2" }, - { name = "pendulum", specifier = ">=3.0.0" }, - { name = "pyiceberg", extras = ["pyarrow"], specifier = "==0.8.0rc1" }, - { name = "tenacity", specifier = ">=8.5.0" }, -] - -[[package]] -name = "dagster-pyiceberg-pandas" -version = "0.0.0" -source = { editable = "packages/dagster_pyiceberg_pandas" } -dependencies = [ - { name = "dagster-pyiceberg" }, - { name = "pandas" }, -] - -[package.metadata] -requires-dist = [ - { name = "dagster-pyiceberg", editable = "packages/dagster_pyiceberg" }, - { name = "pandas", specifier = ">=2.2.3" }, +[package.optional-dependencies] +daft = [ + { name = "getdaft" }, ] - -[[package]] -name = "dagster-pyiceberg-polars" -version = "0.0.0" -source = { editable = "packages/dagster_pyiceberg_polars" } -dependencies = [ - { name = "dagster-pyiceberg" }, +polars = [ { name = "polars" }, ] -[package.metadata] -requires-dist = [ - { name = "dagster-pyiceberg", editable = "packages/dagster_pyiceberg" }, - { name = "polars", specifier = ">=1.11.0" }, -] - -[[package]] -name = "dagster-pyiceberg-project" -version = "0.0.0" -source = { virtual = "." } - [package.dependency-groups] dev = [ { name = "dagit" }, - { name = "dagster-pyiceberg" }, - { name = "dagster-pyiceberg-pandas" }, - { name = "dagster-pyiceberg-polars" }, { name = "ipykernel" }, { name = "pre-commit" }, { name = "psycopg2-binary" }, @@ -531,13 +485,18 @@ docs = [ ] [package.metadata] +requires-dist = [ + { name = "dagster", specifier = ">=1.8.2" }, + { name = "getdaft", marker = "extra == 'daft'", specifier = ">=0.3.13" }, + { name = "pendulum", specifier = ">=3.0.0" }, + { name = "polars", marker = "extra == 'polars'", specifier = ">=1.13.1" }, + { name = "pyiceberg", extras = ["pyarrow"], specifier = "==0.8.0rc1" }, + { name = "tenacity", specifier = ">=8.5.0" }, +] [package.metadata.dependency-groups] dev = [ { name = "dagit", specifier = ">=1.8.8" }, - { name = "dagster-pyiceberg", editable = "packages/dagster_pyiceberg" }, - { name = "dagster-pyiceberg-pandas", editable = "packages/dagster_pyiceberg_pandas" }, - { name = "dagster-pyiceberg-polars", editable = "packages/dagster_pyiceberg_polars" }, { name = "ipykernel", specifier = ">=6.29.5" }, { name = "pre-commit", specifier = ">=3.8.0" }, { name = "psycopg2-binary", specifier = ">=2.9.10" }, @@ -555,7 +514,7 @@ docs = [ [[package]] name = "dagster-webserver" -version = "1.9.1" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -564,9 +523,9 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/79/b8604e0b712f08f5f0740b194c5540cc2198c362095432d71a26e0e56ca0/dagster-webserver-1.9.1.tar.gz", hash = "sha256:562c965d8cf719a1057a575955862042e536e2caa58d547741b9ea3224db6bf0", size = 12343236 } +sdist = { url = "https://files.pythonhosted.org/packages/e3/d4/a309e90d79a9c1de84a604b2a5dc03d8d832ef18e80a217b65028dd64bfc/dagster-webserver-1.9.2.tar.gz", hash = "sha256:34d3252c571c3ed7024dda6684f89dae672b35b820098b490f186100fffcf3b9", size = 12352871 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/79a32b0d9ede28efeae3935361a16f00efef8fb86762ea812adca90b14f5/dagster_webserver-1.9.1-py3-none-any.whl", hash = "sha256:05209dd341e7936a17a9c2ecf0935967b37f5dc405022dffe4a166c5f7d28c4a", size = 12669124 }, + { url = "https://files.pythonhosted.org/packages/b9/d4/0aac9f392d05b4b73d58c1942a7df2f15212befa20111bcae14b4a8e29c1/dagster_webserver-1.9.2-py3-none-any.whl", hash = "sha256:7d8346aa99b96a2e40da79a22e7851898e1dc7ed430002cfd5015d4f2906a2ac", size = 12678889 }, ] [[package]] @@ -671,6 +630,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871", size = 179641 }, ] +[[package]] +name = "getdaft" +version = "0.3.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "pyarrow" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/18/9660891c077d3b771cb57e93408fa475014018cd6c7335aa3abcbb79619d/getdaft-0.3.13.tar.gz", hash = "sha256:d0cbb2e463af5b628c18cd7e182c21dee7f50f5f9d4fe93b4a2ebeb52593e928", size = 3898373 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/17/dd2f9edf01dfb124d64edbb0833afdb767961728b3b0e01ec1328e92a95e/getdaft-0.3.13-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c267a563b41c0997b897c7b354f97e932bf56bf8096fb1040860d629b529cde", size = 29900622 }, + { url = "https://files.pythonhosted.org/packages/df/a1/d1def7b38ca8fb2d45348c12409e60566a012fe977633c1a4142dedb9cb9/getdaft-0.3.13-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:88afa12e888bd408dcb9a6b2cda139c73b8b201baac1eddb4d25eaaefd2804a5", size = 27674539 }, + { url = "https://files.pythonhosted.org/packages/91/eb/c08e633a917e2a4c5f2e895912782216ae616a0ccecc741935d0f203a9e4/getdaft-0.3.13-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7ff3e8a09c8647a2e6fc38bf94eabf9b7c05b8e999ffb7fb02a97bee51049f5", size = 31523766 }, + { url = "https://files.pythonhosted.org/packages/1c/20/dab88809595219135ac5f6d59d8b37deb50e06f58e3fbad7031e53c858b8/getdaft-0.3.13-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f7bfd1ed4915af020975ba9a97074c8852a0f4d55ebac5ceaced4a784b61ca", size = 33134547 }, + { url = "https://files.pythonhosted.org/packages/f6/61/4a97e2f823e538918384a31ade99c3da47c3354f30a664af39378df2d0ff/getdaft-0.3.13-cp38-abi3-win_amd64.whl", hash = "sha256:1a1cef3bf3fdffaa752f3f05994db9eda52a4d97097768aeaeb9abca1d062960", size = 30091518 }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -805,46 +782,46 @@ wheels = [ [[package]] name = "grpcio" -version = "1.67.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/53/d9282a66a5db45981499190b77790570617a604a38f3d103d0400974aeb5/grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732", size = 12580022 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/cd/f6ca5c49aa0ae7bc6d0757f7dae6f789569e9490a635eaabe02bc02de7dc/grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f", size = 5112450 }, - { url = "https://files.pythonhosted.org/packages/d4/f0/d9bbb4a83cbee22f738ee7a74aa41e09ccfb2dcea2cc30ebe8dab5b21771/grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d", size = 10937518 }, - { url = "https://files.pythonhosted.org/packages/5b/17/0c5dbae3af548eb76669887642b5f24b232b021afe77eb42e22bc8951d9c/grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f", size = 5633610 }, - { url = "https://files.pythonhosted.org/packages/17/48/e000614e00153d7b2760dcd9526b95d72f5cfe473b988e78f0ff3b472f6c/grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0", size = 6240678 }, - { url = "https://files.pythonhosted.org/packages/64/19/a16762a70eeb8ddfe43283ce434d1499c1c409ceec0c646f783883084478/grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa", size = 5884528 }, - { url = "https://files.pythonhosted.org/packages/6b/dc/bd016aa3684914acd2c0c7fa4953b2a11583c2b844f3d7bae91fa9b98fbb/grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292", size = 6583680 }, - { url = "https://files.pythonhosted.org/packages/1a/93/1441cb14c874f11aa798a816d582f9da82194b6677f0f134ea53d2d5dbeb/grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311", size = 6162967 }, - { url = "https://files.pythonhosted.org/packages/29/e9/9295090380fb4339b7e935b9d005fa9936dd573a22d147c9e5bb2df1b8d4/grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed", size = 3616336 }, - { url = "https://files.pythonhosted.org/packages/ce/de/7c783b8cb8f02c667ca075c49680c4aeb8b054bc69784bcb3e7c1bbf4985/grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e", size = 4352071 }, - { url = "https://files.pythonhosted.org/packages/59/2c/b60d6ea1f63a20a8d09c6db95c4f9a16497913fb3048ce0990ed81aeeca0/grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb", size = 5119075 }, - { url = "https://files.pythonhosted.org/packages/b3/9a/e1956f7ca582a22dd1f17b9e26fcb8229051b0ce6d33b47227824772feec/grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e", size = 11009159 }, - { url = "https://files.pythonhosted.org/packages/43/a8/35fbbba580c4adb1d40d12e244cf9f7c74a379073c0a0ca9d1b5338675a1/grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f", size = 5629476 }, - { url = "https://files.pythonhosted.org/packages/77/c9/864d336e167263d14dfccb4dbfa7fce634d45775609895287189a03f1fc3/grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc", size = 6239901 }, - { url = "https://files.pythonhosted.org/packages/f7/1e/0011408ebabf9bd69f4f87cc1515cbfe2094e5a32316f8714a75fd8ddfcb/grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96", size = 5881010 }, - { url = "https://files.pythonhosted.org/packages/b4/7d/fbca85ee9123fb296d4eff8df566f458d738186d0067dec6f0aa2fd79d71/grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f", size = 6580706 }, - { url = "https://files.pythonhosted.org/packages/75/7a/766149dcfa2dfa81835bf7df623944c1f636a15fcb9b6138ebe29baf0bc6/grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970", size = 6161799 }, - { url = "https://files.pythonhosted.org/packages/09/13/5b75ae88810aaea19e846f5380611837de411181df51fd7a7d10cb178dcb/grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744", size = 3616330 }, - { url = "https://files.pythonhosted.org/packages/aa/39/38117259613f68f072778c9638a61579c0cfa5678c2558706b10dd1d11d3/grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5", size = 4354535 }, - { url = "https://files.pythonhosted.org/packages/6e/25/6f95bd18d5f506364379eabc0d5874873cc7dbdaf0757df8d1e82bc07a88/grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953", size = 5089809 }, - { url = "https://files.pythonhosted.org/packages/10/3f/d79e32e5d0354be33a12db2267c66d3cfeff700dd5ccdd09fd44a3ff4fb6/grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb", size = 10981985 }, - { url = "https://files.pythonhosted.org/packages/21/f2/36fbc14b3542e3a1c20fb98bd60c4732c55a44e374a4eb68f91f28f14aab/grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0", size = 5588770 }, - { url = "https://files.pythonhosted.org/packages/0d/af/bbc1305df60c4e65de8c12820a942b5e37f9cf684ef5e49a63fbb1476a73/grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af", size = 6214476 }, - { url = "https://files.pythonhosted.org/packages/92/cf/1d4c3e93efa93223e06a5c83ac27e32935f998bc368e276ef858b8883154/grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e", size = 5850129 }, - { url = "https://files.pythonhosted.org/packages/ae/ca/26195b66cb253ac4d5ef59846e354d335c9581dba891624011da0e95d67b/grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75", size = 6568489 }, - { url = "https://files.pythonhosted.org/packages/d1/94/16550ad6b3f13b96f0856ee5dfc2554efac28539ee84a51d7b14526da985/grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38", size = 6149369 }, - { url = "https://files.pythonhosted.org/packages/33/0d/4c3b2587e8ad7f121b597329e6c2620374fccbc2e4e1aa3c73ccc670fde4/grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78", size = 3599176 }, - { url = "https://files.pythonhosted.org/packages/7d/36/0c03e2d80db69e2472cf81c6123aa7d14741de7cf790117291a703ae6ae1/grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc", size = 4346574 }, - { url = "https://files.pythonhosted.org/packages/12/d2/2f032b7a153c7723ea3dea08bffa4bcaca9e0e5bdf643ce565b76da87461/grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b", size = 5091487 }, - { url = "https://files.pythonhosted.org/packages/d0/ae/ea2ff6bd2475a082eb97db1104a903cf5fc57c88c87c10b3c3f41a184fc0/grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1", size = 10943530 }, - { url = "https://files.pythonhosted.org/packages/07/62/646be83d1a78edf8d69b56647327c9afc223e3140a744c59b25fbb279c3b/grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af", size = 5589079 }, - { url = "https://files.pythonhosted.org/packages/d0/25/71513d0a1b2072ce80d7f5909a93596b7ed10348b2ea4fdcbad23f6017bf/grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955", size = 6213542 }, - { url = "https://files.pythonhosted.org/packages/76/9a/d21236297111052dcb5dc85cd77dc7bf25ba67a0f55ae028b2af19a704bc/grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8", size = 5850211 }, - { url = "https://files.pythonhosted.org/packages/2d/fe/70b1da9037f5055be14f359026c238821b9bcf6ca38a8d760f59a589aacd/grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62", size = 6572129 }, - { url = "https://files.pythonhosted.org/packages/74/0d/7df509a2cd2a54814598caf2fb759f3e0b93764431ff410f2175a6efb9e4/grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb", size = 6149819 }, - { url = "https://files.pythonhosted.org/packages/0a/08/bc3b0155600898fd10f16b79054e1cca6cb644fa3c250c0fe59385df5e6f/grpcio-1.67.1-cp313-cp313-win32.whl", hash = "sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121", size = 3596561 }, - { url = "https://files.pythonhosted.org/packages/5a/96/44759eca966720d0f3e1b105c43f8ad4590c97bf8eb3cd489656e9590baa/grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba", size = 4346042 }, +version = "1.68.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/da/132615afbfc722df4bba963844843a205aa298fd5f9a03fa2995e8dddf11/grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a", size = 12682655 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/31/31de69f683298451ec7663cb1e0c36ef07835e9a2b486dfd3665a189f7d1/grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544", size = 5170661 }, + { url = "https://files.pythonhosted.org/packages/0a/7b/caed06de43176982308404aff36141c0af03d279990baad45195254069cf/grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3", size = 11077092 }, + { url = "https://files.pythonhosted.org/packages/57/b1/330966311df3097ca153c38e5ecc7fd872c82485ebda2b999c256cfc20e3/grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a", size = 5689632 }, + { url = "https://files.pythonhosted.org/packages/11/ee/4d2b67b244ecb765cf02ede2e8b8f7d5dbb9d6cd8290d6edc495dbade950/grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121", size = 6316787 }, + { url = "https://files.pythonhosted.org/packages/d0/64/3a90fac70af2279477483d189007e244fec90ef0ba372043f21f8a6f5b80/grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110", size = 5941966 }, + { url = "https://files.pythonhosted.org/packages/cc/a4/a30b344da85d680d834fed903e78c44a33028eadb9efae6850a7d398fd9b/grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618", size = 6646172 }, + { url = "https://files.pythonhosted.org/packages/c7/5a/b97371b4dbecbae31cafee6655f1982193a6e6412da2808c87c9b1734589/grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1", size = 6212496 }, + { url = "https://files.pythonhosted.org/packages/47/df/dc797a6cb827fc6cf1ed4da812b3721b7705869d84d50d112543bb4ecf1b/grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b", size = 3649704 }, + { url = "https://files.pythonhosted.org/packages/24/9a/91796bf4d7c6adb867add0ea37f83ea29d8a9743809f3478e5d284926775/grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a", size = 4399061 }, + { url = "https://files.pythonhosted.org/packages/cf/5f/019594ff8130ce84f9317cfc1e3d2c2beef2b74fd8822c5f1dfe237cb0d5/grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415", size = 5180685 }, + { url = "https://files.pythonhosted.org/packages/7b/59/34dae935bbb42f3e8929c90e9dfff49090cef412cf767cf4f14cd01ded18/grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155", size = 11150577 }, + { url = "https://files.pythonhosted.org/packages/a6/5e/3df718124aadfc5d565c70ebe6a32c9ee747a9ccf211041596dd471fd763/grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c", size = 5685490 }, + { url = "https://files.pythonhosted.org/packages/4c/57/4e39ac1030875e0497debc9d5a4b3a1478ee1bd957ba4b87c27fcd7a3545/grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4", size = 6316329 }, + { url = "https://files.pythonhosted.org/packages/26/fe/9208707b0c07d28bb9f466340e4f052142fe40d54ea5c2d57870ba0d6860/grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30", size = 5939890 }, + { url = "https://files.pythonhosted.org/packages/05/b9/e344bf744e095e2795fe942ce432add2d03761c3c440a5747705ff5b8efb/grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1", size = 6644776 }, + { url = "https://files.pythonhosted.org/packages/ef/bf/0856c5fa93c3e1bd9f42da62a7aa6988c7a8f95f30dc4f9a3d631f75bb8e/grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75", size = 6211889 }, + { url = "https://files.pythonhosted.org/packages/63/40/eac5203baf7f45c56b16645c81a4c8ed515510fe81322371e8625758239b/grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc", size = 3650597 }, + { url = "https://files.pythonhosted.org/packages/e4/31/120ec7132e6b82a0df91952f71aa0aa5e9f23d70152b58d96fac9b3e7cfe/grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27", size = 4400445 }, + { url = "https://files.pythonhosted.org/packages/30/66/79508e13feee4182e6f2ea260ad4eea96b8b396bbf81334660142a6eecab/grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d", size = 5147575 }, + { url = "https://files.pythonhosted.org/packages/41/8d/19ffe12a736f57e9860bad506c0e711dd3c9c7c9f06030cfd87fa3eb6b45/grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7", size = 11126767 }, + { url = "https://files.pythonhosted.org/packages/9c/c6/9aa8178d0fa3c893531a3ef38fa65a0e9997047ded9a8a20e3aa5706f923/grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d", size = 5644649 }, + { url = "https://files.pythonhosted.org/packages/36/91/e2c451a103b8b595d3e3725fc78c76242d38a96cfe22dd9a47c31faba99d/grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b", size = 6292623 }, + { url = "https://files.pythonhosted.org/packages/0b/5f/cbb2c0dfb3f7b893b30d6daca0a7829067f302c55f20b9c470111f48e6e3/grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe", size = 5905873 }, + { url = "https://files.pythonhosted.org/packages/9d/37/ddc32a46baccac6a0a3cdcabd6908d23dfa526f061a1b81211fe029489c7/grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd", size = 6630863 }, + { url = "https://files.pythonhosted.org/packages/45/69/4f74f67ae33be4422bd20050e09ad8b5318f8827a7eb153507de8fb78aef/grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659", size = 6200368 }, + { url = "https://files.pythonhosted.org/packages/91/e9/25e51915cd972e8c66daf29644e653135f967d7411eccd2651fa347a6337/grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332", size = 3637786 }, + { url = "https://files.pythonhosted.org/packages/e2/1d/b1250907a727f08de6508d752f367e4b46d113d4eac9eb919ebd9da6a5d6/grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9", size = 4390622 }, + { url = "https://files.pythonhosted.org/packages/fb/2d/d9cbdb75dc99141705f08474e97b181034c2e53a345d94b58e3c55f4dd92/grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e", size = 5149697 }, + { url = "https://files.pythonhosted.org/packages/6f/37/a848871a5adba8cd571fa89e8aabc40ca0c475bd78b2e645e1649b20e095/grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1", size = 11084394 }, + { url = "https://files.pythonhosted.org/packages/1f/52/b09374aab9c9c2f66627ce7de39eef41d73670aa0f75286d91dcc22a2dd8/grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b", size = 5645417 }, + { url = "https://files.pythonhosted.org/packages/01/78/ec5ad7c44d7adaf0b932fd41ce8c59a95177a8c79c947c77204600b652db/grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb", size = 6291062 }, + { url = "https://files.pythonhosted.org/packages/f7/7f/7f5a1a8dc63a42b78ca930d195eb0c97aa7a09e8553bb3a07b7cf37f6bc1/grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21", size = 5906505 }, + { url = "https://files.pythonhosted.org/packages/41/7b/0b048b8ad1a09fab5f4567fba2a569fb9106c4c1bb473c009c25659542cb/grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d", size = 6635069 }, + { url = "https://files.pythonhosted.org/packages/5e/c5/9f0ebc9cfba8309a15a9786c953ce99eaf4e1ca2df402b3c5ecf42493bd4/grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665", size = 6200683 }, + { url = "https://files.pythonhosted.org/packages/ce/e1/d3eba05299d5acdae6c11d056308b885f1d1be0b328baa8233d5d139ec1d/grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03", size = 3637301 }, + { url = "https://files.pythonhosted.org/packages/3c/c1/decb2b368a54c00a6ee815c3f610903f36432e3cb591d43369319826b05e/grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a", size = 4390939 }, ] [[package]] @@ -1461,68 +1438,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] -[[package]] -name = "numpy" -version = "2.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/80/d572a4737626372915bca41c3afbfec9d173561a39a0a61bacbbfd1dafd4/numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", size = 21152472 }, - { url = "https://files.pythonhosted.org/packages/6f/bb/7bfba10c791ae3bb6716da77ad85a82d5fac07fc96fb0023ef0571df9d20/numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", size = 13747967 }, - { url = "https://files.pythonhosted.org/packages/da/d6/2df7bde35f0478455f0be5934877b3e5a505f587b00230f54a519a6b55a5/numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", size = 5354921 }, - { url = "https://files.pythonhosted.org/packages/d1/bb/75b945874f931494891eac6ca06a1764d0e8208791f3addadb2963b83527/numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", size = 6888603 }, - { url = "https://files.pythonhosted.org/packages/68/a7/fde73636f6498dbfa6d82fc336164635fe592f1ad0d13285fcb6267fdc1c/numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", size = 13889862 }, - { url = "https://files.pythonhosted.org/packages/05/db/5d9c91b2e1e2e72be1369278f696356d44975befcae830daf2e667dcb54f/numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", size = 16328151 }, - { url = "https://files.pythonhosted.org/packages/3e/6a/7eb732109b53ae64a29e25d7e68eb9d6611037f6354875497008a49e74d3/numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", size = 16704107 }, - { url = "https://files.pythonhosted.org/packages/88/cc/278113b66a1141053cbda6f80e4200c6da06b3079c2d27bda1fde41f2c1f/numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4", size = 14385789 }, - { url = "https://files.pythonhosted.org/packages/f5/69/eb20f5e1bfa07449bc67574d2f0f7c1e6b335fb41672e43861a7727d85f2/numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", size = 6536706 }, - { url = "https://files.pythonhosted.org/packages/8e/8b/1c131ab5a94c1086c289c6e1da1d843de9dbd95fe5f5ee6e61904c9518e2/numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", size = 12864165 }, - { url = "https://files.pythonhosted.org/packages/ad/81/c8167192eba5247593cd9d305ac236847c2912ff39e11402e72ae28a4985/numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", size = 21156252 }, - { url = "https://files.pythonhosted.org/packages/da/74/5a60003fc3d8a718d830b08b654d0eea2d2db0806bab8f3c2aca7e18e010/numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", size = 13784119 }, - { url = "https://files.pythonhosted.org/packages/47/7c/864cb966b96fce5e63fcf25e1e4d957fe5725a635e5f11fe03f39dd9d6b5/numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", size = 5352978 }, - { url = "https://files.pythonhosted.org/packages/09/ac/61d07930a4993dd9691a6432de16d93bbe6aa4b1c12a5e573d468eefc1ca/numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", size = 6892570 }, - { url = "https://files.pythonhosted.org/packages/27/2f/21b94664f23af2bb52030653697c685022119e0dc93d6097c3cb45bce5f9/numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", size = 13896715 }, - { url = "https://files.pythonhosted.org/packages/7a/f0/80811e836484262b236c684a75dfc4ba0424bc670e765afaa911468d9f39/numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", size = 16339644 }, - { url = "https://files.pythonhosted.org/packages/fa/81/ce213159a1ed8eb7d88a2a6ef4fbdb9e4ffd0c76b866c350eb4e3c37e640/numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", size = 16712217 }, - { url = "https://files.pythonhosted.org/packages/7d/84/4de0b87d5a72f45556b2a8ee9fc8801e8518ec867fc68260c1f5dcb3903f/numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", size = 14399053 }, - { url = "https://files.pythonhosted.org/packages/7e/1c/e5fabb9ad849f9d798b44458fd12a318d27592d4bc1448e269dec070ff04/numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", size = 6534741 }, - { url = "https://files.pythonhosted.org/packages/1e/48/a9a4b538e28f854bfb62e1dea3c8fea12e90216a276c7777ae5345ff29a7/numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", size = 12869487 }, - { url = "https://files.pythonhosted.org/packages/8a/f0/385eb9970309643cbca4fc6eebc8bb16e560de129c91258dfaa18498da8b/numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", size = 20849658 }, - { url = "https://files.pythonhosted.org/packages/54/4a/765b4607f0fecbb239638d610d04ec0a0ded9b4951c56dc68cef79026abf/numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", size = 13492258 }, - { url = "https://files.pythonhosted.org/packages/bd/a7/2332679479c70b68dccbf4a8eb9c9b5ee383164b161bee9284ac141fbd33/numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", size = 5090249 }, - { url = "https://files.pythonhosted.org/packages/c1/67/4aa00316b3b981a822c7a239d3a8135be2a6945d1fd11d0efb25d361711a/numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", size = 6621704 }, - { url = "https://files.pythonhosted.org/packages/5e/da/1a429ae58b3b6c364eeec93bf044c532f2ff7b48a52e41050896cf15d5b1/numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", size = 13606089 }, - { url = "https://files.pythonhosted.org/packages/9e/3e/3757f304c704f2f0294a6b8340fcf2be244038be07da4cccf390fa678a9f/numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", size = 16043185 }, - { url = "https://files.pythonhosted.org/packages/43/97/75329c28fea3113d00c8d2daf9bc5828d58d78ed661d8e05e234f86f0f6d/numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", size = 16410751 }, - { url = "https://files.pythonhosted.org/packages/ad/7a/442965e98b34e0ae9da319f075b387bcb9a1e0658276cc63adb8c9686f7b/numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", size = 14082705 }, - { url = "https://files.pythonhosted.org/packages/ac/b6/26108cf2cfa5c7e03fb969b595c93131eab4a399762b51ce9ebec2332e80/numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", size = 6239077 }, - { url = "https://files.pythonhosted.org/packages/a6/84/fa11dad3404b7634aaab50733581ce11e5350383311ea7a7010f464c0170/numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", size = 12566858 }, - { url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 }, - { url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 }, - { url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 }, - { url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 }, - { url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 }, - { url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 }, - { url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 }, - { url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 }, - { url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 }, - { url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 }, - { url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 }, - { url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 }, - { url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 }, - { url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 }, - { url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 }, - { url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 }, - { url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 }, - { url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 }, - { url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 }, - { url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 }, - { url = "https://files.pythonhosted.org/packages/00/e7/8d8bb791b62586cc432ecbb70632b4f23b7b7c88df41878de7528264f6d7/numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", size = 20983893 }, - { url = "https://files.pythonhosted.org/packages/5e/f3/cb8118a044b5007586245a650360c9f5915b2f4232dd7658bb7a63dd1d02/numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", size = 6752501 }, - { url = "https://files.pythonhosted.org/packages/53/f5/365b46439b518d2ec6ebb880cc0edf90f225145dfd4db7958334f7164530/numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", size = 16142601 }, - { url = "https://files.pythonhosted.org/packages/03/c2/d1fee6ba999aa7cd41ca6856937f2baaf604c3eec1565eae63451ec31e5e/numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", size = 12771397 }, -] - [[package]] name = "packaging" version = "24.2" @@ -1541,54 +1456,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, ] -[[package]] -name = "pandas" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, - { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, - { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, - { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, - { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, - { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, - { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, - { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, - { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, - { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, - { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, - { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, - { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, - { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, - { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, - { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, - { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, - { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, - { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, - { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, - { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, - { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, - { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, - { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, - { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, - { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, - { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, - { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, - { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, - { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, - { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, - { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, - { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, - { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, -] - [[package]] name = "parso" version = "0.8.4"