From 76347b1b4cdfd84e1089e4649902afdb4d3a944d Mon Sep 17 00:00:00 2001 From: jonavellecuerdo Date: Mon, 9 Dec 2024 12:38:12 -0500 Subject: [PATCH 1/4] Rework dataset partitions to only year, month, day Why these changes are being introduced: * These changes simplify the partitioning schema for the TIMDEXDataset, allowing the app to take advantage of PyArrow's memory-efficient processes for reading and writing Parquet datasets. Furthermore, the new partitioning schema will result in a more efficient, coherent folder structure when writing datasets. For more details, see: https://mitlibraries.atlassian.net/wiki/spaces/IN/pages/4094296066/Engineering+Plan+Parquet+Datasets+for+TIMDEX+ETL#Rework-Dataset-Partitions-to-use-only-Year-%2F-Month-%2F-Day. How this addresses that need: * Update TIMDEX_DATASET_SCHEMA to include [year, month, day] * Update DatasetRecord attrs to include [year, month, day] and set [source, run_date, run_type, run_id, action] as primary columns * Add post_init method to DatasetRecord to derive partition values from 'run-date * Remove 'partition' values from DatasetRecord.to_dict * Remove 'partition_values' mixin from TIMDEXDataset.write to reduce complexity and have write method utilize DatasetRecord partition columns instead. * Update unit tests to use new partitions and remove deprecated tests Side effects of this change: * The new partitioning schema introduces a 3-level folder structure within TIMDEXDataset.location (i.e. the base path of the dataset) for [year, month, day], where the leaf node will contain parquet files for every source run. Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/TIMX-432 --- .gitignore | 3 + tests/conftest.py | 7 +- tests/test_dataset_write.py | 143 ++++++++-------------------------- tests/utils.py | 6 ++ timdex_dataset_api/dataset.py | 32 +++----- timdex_dataset_api/record.py | 42 ++++++---- 6 files changed, 80 insertions(+), 153 deletions(-) diff --git a/.gitignore b/.gitignore index eff8c02..3bc59df 100644 --- a/.gitignore +++ b/.gitignore @@ -156,3 +156,6 @@ cython_debug/ # PyCharm .idea/ + +# VSCode +.vscode diff --git a/tests/conftest.py b/tests/conftest.py index 84dbfc0..e2da0a3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,12 +51,7 @@ def sample_records_iter_without_partitions(): def _records_iter(num_records): return generate_sample_records( - num_records, - source=None, - run_date=None, - run_type=None, - action=None, - run_id=None, + num_records, run_date="invalid run-date", year=None, month=None, day=None ) return _records_iter diff --git a/tests/test_dataset_write.py b/tests/test_dataset_write.py index 3852544..abeac95 100644 --- a/tests/test_dataset_write.py +++ b/tests/test_dataset_write.py @@ -1,12 +1,13 @@ # ruff: noqa: S105, S106, SLF001, PLR2004, PD901, D209, D205 -import datetime import math import os +import re import pyarrow.dataset as ds import pytest +from tests.utils import generate_sample_records from timdex_dataset_api.dataset import ( MAX_ROWS_PER_FILE, TIMDEX_DATASET_SCHEMA, @@ -17,7 +18,7 @@ from timdex_dataset_api.record import DatasetRecord -def test_dataset_record_serialization(): +def test_dataset_record_init(): values = { "timdex_record_id": "alma:123", "source_record": b"Hello World.", @@ -26,38 +27,38 @@ def test_dataset_record_serialization(): "run_date": "2024-12-01", "run_type": "full", "action": "index", - "run_id": "abc123", + "run_id": "000-111-aaa-bbb", + "year": 2024, + "month": 12, + "day": 1, } - dataset_record = DatasetRecord(**values) - assert dataset_record.to_dict() == values + assert DatasetRecord(**values) -def test_dataset_record_serialization_with_partition_values_provided(): - dataset_record = DatasetRecord( - timdex_record_id="alma:123", - source_record=b"Hello World.", - transformed_record=b"""{"title":["Hello World."]}""", - ) - partition_values = { - "source": "alma", - "run_date": "2024-12-01", - "run_type": "daily", - "action": "index", - "run_id": "000-111-aaa-bbb", - } - assert dataset_record.to_dict(partition_values=partition_values) == { +def test_dataset_record_init_with_invalid_run_date_raise_error(): + values = { "timdex_record_id": "alma:123", "source_record": b"Hello World.", "transformed_record": b"""{"title":["Hello World."]}""", - "source": "alma", - "run_date": "2024-12-01", - "run_type": "daily", + "source": "libguides", + "run_date": "-12-01", + "run_type": "full", "action": "index", "run_id": "000-111-aaa-bbb", + "year": None, + "month": None, + "day": None, } + with pytest.raises( + InvalidDatasetRecordError, + match=re.escape( + "Cannot parse partition values [year, month, date] from invalid 'run-date' string." # noqa: E501 + ), + ): + DatasetRecord(**values) -def test_dataset_record_serialization_missing_partition_raise_error(): +def test_dataset_record_serialization(): values = { "timdex_record_id": "alma:123", "source_record": b"Hello World.", @@ -66,14 +67,13 @@ def test_dataset_record_serialization_missing_partition_raise_error(): "run_date": "2024-12-01", "run_type": "full", "action": "index", - "run_id": None, # <------ missing partition here + "run_id": "abc123", + "year": "2024", + "month": "12", + "day": "01", } dataset_record = DatasetRecord(**values) - with pytest.raises( - InvalidDatasetRecordError, - match="Partition values are missing: run_id", - ): - assert dataset_record.to_dict() == values + assert dataset_record.to_dict() == values def test_dataset_write_records_to_new_dataset(new_dataset, sample_records_iter): @@ -134,52 +134,6 @@ def test_dataset_write_to_multiple_locations_raise_error(sample_records_iter): timdex_dataset.write(sample_records_iter(10)) -def test_dataset_write_mixin_partition_values_used( - new_dataset, sample_records_iter_without_partitions -): - partition_values = { - "source": "alma", - "run_date": "2024-12-01", - "run_type": "daily", - "action": "index", - "run_id": "000-111-aaa-bbb", - } - _written_files = new_dataset.write( - sample_records_iter_without_partitions(10), - partition_values=partition_values, - ) - new_dataset.reload() - - # load as pandas dataframe and assert column values - df = new_dataset.dataset.to_table().to_pandas() - row = df.iloc[0] - assert row.source == partition_values["source"] - assert row.run_date == datetime.date(2024, 12, 1) - assert row.run_type == partition_values["run_type"] - assert row.action == partition_values["action"] - assert row.action == partition_values["action"] - - -def test_dataset_write_schema_partitions_correctly_ordered( - new_dataset, sample_records_iter -): - written_files = new_dataset.write( - sample_records_iter(10), - partition_values={ - "source": "alma", - "run_date": "2024-12-01", - "run_type": "daily", - "run_id": "000-111-aaa-bbb", - "action": "index", - }, - ) - file = written_files[0] - assert ( - "/source=alma/run_date=2024-12-01/run_type=daily" - "/run_id=000-111-aaa-bbb/action=index/" in file.path - ) - - def test_dataset_write_schema_applied_to_dataset(new_dataset, sample_records_iter): new_dataset.write(sample_records_iter(10)) @@ -199,38 +153,20 @@ def test_dataset_write_partition_deleted_when_written_to_again( ): """This tests the existing_data_behavior="delete_matching" configuration when writing to a dataset.""" - partition_values = { - "source": "alma", - "run_date": "2024-12-01", - "run_type": "daily", - "action": "index", - "run_id": "000-111-aaa-bbb", - } - # perform FIRST write to run_date="2024-12-01" - written_files_1 = new_dataset.write( - sample_records_iter(10), - partition_values=partition_values, - ) + written_files_1 = new_dataset.write(sample_records_iter(10)) # assert that files from first write are present at this time assert os.path.exists(written_files_1[0].path) # perform unrelated write with new run_date to confirm this is untouched during delete - new_partition_values = partition_values.copy() - new_partition_values["run_date"] = "2024-12-15" - new_partition_values["run_id"] = "222-333-ccc-ddd" written_files_x = new_dataset.write( - sample_records_iter(7), - partition_values=new_partition_values, + generate_sample_records(7, run_date="2024-12-15"), ) # perform SECOND write to run_date="2024-12-01", expecting this to delete everything # under this combination of partitions (i.e. the first write) - written_files_2 = new_dataset.write( - sample_records_iter(10), - partition_values=partition_values, - ) + written_files_2 = new_dataset.write(sample_records_iter(10)) new_dataset.reload() @@ -243,18 +179,3 @@ def test_dataset_write_partition_deleted_when_written_to_again( assert not os.path.exists(written_files_1[0].path) assert os.path.exists(written_files_2[0].path) assert os.path.exists(written_files_x[0].path) - - -def test_dataset_write_missing_partitions_raise_error(new_dataset, sample_records_iter): - missing_partition_values = { - "source": "libguides", - "run_date": None, - "run_type": None, - "action": None, - "run_id": None, - } - with pytest.raises(InvalidDatasetRecordError, match="Partition values are missing"): - _ = new_dataset.write( - sample_records_iter(10), - partition_values=missing_partition_values, - ) diff --git a/tests/utils.py b/tests/utils.py index fc8a420..68853f9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -17,6 +17,9 @@ def generate_sample_records( run_type: str | None = "daily", action: str | None = "index", run_id: str | None = None, + year: str | int | None = "2024", + month: str | int | None = "12", + day: str | int | None = "1", ) -> Iterator[DatasetRecord]: """Generate sample DatasetRecords.""" if not run_id: @@ -32,6 +35,9 @@ def generate_sample_records( run_type=run_type, action=action, run_id=run_id, + year=year, + month=month, + day=day, ) diff --git a/timdex_dataset_api/dataset.py b/timdex_dataset_api/dataset.py index 50ebdad..e339810 100644 --- a/timdex_dataset_api/dataset.py +++ b/timdex_dataset_api/dataset.py @@ -1,6 +1,5 @@ """timdex_dataset_api/dataset.py""" -import datetime import itertools import time import uuid @@ -30,15 +29,16 @@ pa.field("run_type", pa.string()), pa.field("run_id", pa.string()), pa.field("action", pa.string()), + pa.field("year", pa.string()), + pa.field("month", pa.string()), + pa.field("day", pa.string()), ) ) TIMDEX_DATASET_PARTITION_COLUMNS = [ - "source", - "run_date", - "run_type", - "run_id", - "action", + "year", + "month", + "day", ] DEFAULT_BATCH_SIZE = 1_000 @@ -166,16 +166,12 @@ def write( self, records_iter: Iterator["DatasetRecord"], *, - partition_values: dict[str, str | datetime.datetime] | None = None, batch_size: int = DEFAULT_BATCH_SIZE, use_threads: bool = True, ) -> list[ds.WrittenFile]: """Write records to the TIMDEX parquet dataset. - This method expects an iterator of DatasetRecord instances, with optional - partition column values that will be applied to all rows written (often, these - are the same for all rows written, eliminating the need to repeat those values - in the iterator). + This method expects an iterator of DatasetRecord instances. This method encapsulates all dataset writing mechanics and performance optimizations (e.g. batching) so that the calling context can focus on yielding @@ -192,7 +188,6 @@ def write( Args: - records_iter: Iterator of DatasetRecord instances - - partition_values: dictionary of static partition column name/value pairs - batch_size: size for batches to yield and write, directly affecting row group size in final parquet files - use_threads: boolean if threads should be used for writing @@ -207,7 +202,6 @@ def write( record_batches_iter = self.get_dataset_record_batches( records_iter, - partition_values=partition_values, batch_size=batch_size, ) @@ -235,32 +229,24 @@ def get_dataset_record_batches( self, records_iter: Iterator["DatasetRecord"], *, - partition_values: dict[str, str | datetime.datetime] | None = None, batch_size: int = DEFAULT_BATCH_SIZE, ) -> Iterator[pa.RecordBatch]: """Yield pyarrow.RecordBatches for writing. - This method expects an iterator of DatasetRecord instances, with optional - partition column values that will be applied to all rows written (often, these - are the same for all rows written, eliminating the need to repeat those values - in the iterator). + This method expects an iterator of DatasetRecord instances. Each DatasetRecord is validated and serialized to a dictionary before added to a pyarrow.RecordBatch for writing. Args: - records_iter: Iterator of DatasetRecord instances - - partition_values: dictionary of static partition column name/value pairs - batch_size: size for batches to yield and write, directly affecting row group size in final parquet files """ for i, record_batch in enumerate(itertools.batched(records_iter, batch_size)): batch_start_time = time.perf_counter() batch = pa.RecordBatch.from_pylist( - [ - record.to_dict(partition_values=partition_values) - for record in record_batch - ] + [record.to_dict() for record in record_batch] ) logger.debug( f"Batch {i + 1} yielded for writing, " diff --git a/timdex_dataset_api/record.py b/timdex_dataset_api/record.py index ddc2b3d..95531b4 100644 --- a/timdex_dataset_api/record.py +++ b/timdex_dataset_api/record.py @@ -19,35 +19,51 @@ class DatasetRecord: timdex_record_id: str source_record: bytes transformed_record: bytes + source: str + run_date: str | datetime.datetime + run_type: str + run_id: str + action: str # partition columns - source: str | None = None - run_date: str | datetime.datetime | None = None - run_type: str | None = None - run_id: str | None = None - action: str | None = None + year: str | None = None + month: str | None = None + day: str | None = None + + def __post_init__(self) -> None: + """Post init method to derive partition values from self.run_date""" + run_date = self.run_date + + if isinstance(run_date, str): + try: + run_date = datetime.datetime.strptime(run_date, "%Y-%m-%d").astimezone( + datetime.UTC + ) + except ValueError as exception: + raise InvalidDatasetRecordError( + "Cannot parse partition values [year, month, date] from invalid 'run-date' string." # noqa: E501 + ) from exception + + self.year = run_date.strftime("%Y") + self.month = run_date.strftime("%m") + self.day = run_date.strftime("%d") def to_dict( self, *, - partition_values: dict[str, str | datetime.datetime] | None = None, validate: bool = True, ) -> dict: - """Serialize instance as dictionary, setting partition values if passed.""" - if partition_values: - for key, value in partition_values.items(): - setattr(self, key, value) + """Serialize instance as dictionary.""" if validate: self.validate() + return asdict(self) def validate(self) -> None: """Validate DatasetRecord for writing.""" # ensure all partition columns are set missing_partition_values = [ - field - for field in ["source", "run_date", "run_type", "run_id", "action"] - if getattr(self, field) is None + field for field in ["year", "month", "day"] if getattr(self, field) is None ] if missing_partition_values: raise InvalidDatasetRecordError( From 0849ee32f0ad68ff90f23b6ad70d0a0c52b40a16 Mon Sep 17 00:00:00 2001 From: jonavellecuerdo Date: Wed, 11 Dec 2024 13:19:43 -0500 Subject: [PATCH 2/4] Update TIMDEXDataset.write method to only overwrite similarly named parquet files Why these changes are being introduced: * Since the TIMDEXDataset partitions are now the [year, month, day] of the 'run_date', parquet files from different source runs will be written to the same partition. The previous configuration of existing_data_behavior="delete_matching" would result in the deletion of any existing parquet files from the partition directory with every source run, which is not the desired outcome. To support the new partitions, this updates the configuration existing_data_behavior="overwrite_or_ignore" which will ignore any existing data and will only overwrite files with the same filename. How this addresses that need: * Set existing_data_behavior="overwrite_or_ignore" in ds.write_dataset method call * Add unit tests to demonstrate updated existing_data_behavior Side effects of this change: * In the event the multiple runs are performed for the same 'source' and 'run-date', which is unlikely to occur, parquet files from both runs will exist in the partitioned directory. DatasetRecords are can still be uniquely identified via the 'run_id' column. Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/TIMX-432 --- tests/test_dataset_write.py | 76 ++++++++++++++++++++++++----------- timdex_dataset_api/dataset.py | 11 ++--- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/tests/test_dataset_write.py b/tests/test_dataset_write.py index abeac95..d35369d 100644 --- a/tests/test_dataset_write.py +++ b/tests/test_dataset_write.py @@ -3,6 +3,7 @@ import math import os import re +from unittest.mock import patch import pyarrow.dataset as ds import pytest @@ -148,34 +149,63 @@ def test_dataset_write_schema_applied_to_dataset(new_dataset, sample_records_ite assert set(dataset.schema.names) == set(TIMDEX_DATASET_SCHEMA.names) -def test_dataset_write_partition_deleted_when_written_to_again( - new_dataset, sample_records_iter -): - """This tests the existing_data_behavior="delete_matching" configuration when writing - to a dataset.""" - # perform FIRST write to run_date="2024-12-01" - written_files_1 = new_dataset.write(sample_records_iter(10)) +def test_dataset_write_partition_for_single_source(new_dataset, sample_records_iter): + written_files = new_dataset.write(sample_records_iter(10)) + assert len(written_files) == 1 + assert os.path.exists(new_dataset.location) + assert "year=2024/month=12/day=01" in written_files[0].path - # assert that files from first write are present at this time - assert os.path.exists(written_files_1[0].path) - # perform unrelated write with new run_date to confirm this is untouched during delete - written_files_x = new_dataset.write( - generate_sample_records(7, run_date="2024-12-15"), - ) +def test_dataset_write_partition_for_multiple_sources(new_dataset, sample_records_iter): + # perform write for source="alma" and run_date="2024-12-01" + written_files_source_a = new_dataset.write(sample_records_iter(10)) + new_dataset.reload() - # perform SECOND write to run_date="2024-12-01", expecting this to delete everything - # under this combination of partitions (i.e. the first write) - written_files_2 = new_dataset.write(sample_records_iter(10)) + assert os.path.exists(written_files_source_a[0].path) + assert new_dataset.row_count == 10 + # perform write for source="libguides" and run_date="2024-12-01" + written_files_source_b = new_dataset.write( + generate_sample_records( + num_records=7, timdex_record_id_prefix="libguides", source="libguides" + ) + ) new_dataset.reload() - # assert 17 rows: second write for run_date="2024-12-01" @ 10 rows + - # run_date="2024-12-15" @ 5 rows + assert os.path.exists(written_files_source_b[0].path) + assert os.path.exists(written_files_source_a[0].path) assert new_dataset.row_count == 17 - # assert that files from first run_date="2024-12-01" are gone, second exist - # and files from run_date="2024-12-15" also exist - assert not os.path.exists(written_files_1[0].path) - assert os.path.exists(written_files_2[0].path) - assert os.path.exists(written_files_x[0].path) + +def test_dataset_write_partition_ignore_existing_data(new_dataset, sample_records_iter): + # perform two (2) writes for source="alma" and run_date="2024-12-01" + written_files_source_a0 = new_dataset.write(sample_records_iter(10)) + written_files_source_a1 = new_dataset.write(sample_records_iter(10)) + new_dataset.reload() + + # assert that both files exist and no overwriting occurs + assert os.path.exists(written_files_source_a0[0].path) + assert os.path.exists(written_files_source_a1[0].path) + assert new_dataset.row_count == 20 + + +@patch("timdex_dataset_api.dataset.uuid.uuid4") +def test_dataset_write_partition_overwrite_files_with_same_name( + mock_uuid, new_dataset, sample_records_iter +): + """This test is to demonstrate existing_data_behavior="overwrite_or_ignore". + + It is extremely unlikely for the uuid.uuid4 method to generate duplicate values, + so for testing purposes, this method is patched to return the same value + and therefore generate similarly named files. + """ + mock_uuid.return_value = "abc" + + # perform two (2) writes for source="alma" and run_date="2024-12-01" + _ = new_dataset.write(sample_records_iter(10)) + written_files_source_a1 = new_dataset.write(sample_records_iter(7)) + new_dataset.reload() + + # assert that only the second file exists and overwriting occurs + assert os.path.exists(written_files_source_a1[0].path) + assert new_dataset.row_count == 7 diff --git a/timdex_dataset_api/dataset.py b/timdex_dataset_api/dataset.py index e339810..4ce10b4 100644 --- a/timdex_dataset_api/dataset.py +++ b/timdex_dataset_api/dataset.py @@ -177,10 +177,11 @@ def write( optimizations (e.g. batching) so that the calling context can focus on yielding data. - For write, the configuration existing_data_behavior="delete_matching" is used. - This means that during write, if any pre-existing files are found for the exact - combinations of partitions for that batch, those pre-existing files will be - deleted. This effectively makes a write idempotent to the TIMDEX dataset. + This method uses the configuration existing_data_behavior="overwrite_or_ignore", + which will ignore any existing data and will overwrite files with the same name + as the parquet file. Since a UUID is generated for each write via the + basename_template, this effectively makes a write idempotent to the + TIMDEX dataset. A max_open_files=500 configuration is set to avoid AWS S3 503 error "SLOW_DOWN" if too many PutObject calls are made in parallel. Testing suggests this does not @@ -209,7 +210,7 @@ def write( record_batches_iter, base_dir=self.source, basename_template="%s-{i}.parquet" % (str(uuid.uuid4())), # noqa: UP031 - existing_data_behavior="delete_matching", + existing_data_behavior="overwrite_or_ignore", filesystem=self.filesystem, file_visitor=lambda written_file: self._written_files.append(written_file), # type: ignore[arg-type] format="parquet", From 0235289fab7d22aedadc52bed219b909c1ab14e2 Mon Sep 17 00:00:00 2001 From: jonavellecuerdo Date: Wed, 11 Dec 2024 15:00:10 -0500 Subject: [PATCH 3/4] Refactor DatasetRecord to use attrs Why these changes are being introduced: * Reworking the dataset partitions to use the [year, month, day] of the 'run_date' means that parquet files for different 'source' runs on the same 'run_date' get written to the same partition directory. Therefore, it is crucial that the timdex_dataset_api.write method retrieves the correct partition columns from the (batches) of DatasetRecord objects. The DatasetRecord class has been refactored to adhere to the following criteria: 1. When writing to the dataset, and therefore serializing DatasetRecord objects, year, month, day should be derived from the run_date and should not be modifiable 2. If possible, avoid parsing a datetime string 3 times for each partition column How this addresses that need: * Refactor DatasetRecord to use attrs * Define custom strict_date_parse converter method for 'run_date' field * Simplify serialization method to rely on converter for 'run_date' error handling * Remove DatasetRecord.validate * Include attrs as a dependency Side effects of this change: * None Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/TIMX-432 --- Pipfile | 8 +- Pipfile.lock | 476 ++++++++++++++++++----------------- tests/test_dataset_write.py | 35 +-- tests/utils.py | 6 - timdex_dataset_api/record.py | 85 +++---- 5 files changed, 303 insertions(+), 307 deletions(-) diff --git a/Pipfile b/Pipfile index 43516c1..aa9f89c 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ verify_ssl = true name = "pypi" [packages] +attrs = "*" boto3 = "*" duckdb = "*" pandas = "*" @@ -14,15 +15,14 @@ black = "*" boto3-stubs = {version = "*", extras = ["s3"]} coveralls = "*" ipython = "*" +moto = "*" mypy = "*" +pandas-stubs = "*" pre-commit = "*" +pytest-mock = "*" pyarrow-stubs = "*" pytest = "*" ruff = "*" setuptools = "*" -pandas-stubs = "*" -moto = "*" -pytest-mock = "*" - [requires] python_version = "3.12" diff --git a/Pipfile.lock b/Pipfile.lock index 7b97bef..2074530 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3f9d0481f2dc996f511acff686489d2a2bb88469cb7ec87e769b77e6c53b2549" + "sha256": "d9e081653de47fb0c08e6c6f8e86f46e91d9f55868a240d088603e4af2dd91e3" }, "pipfile-spec": 6, "requires": { @@ -16,22 +16,31 @@ ] }, "default": { + "attrs": { + "hashes": [ + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==24.2.0" + }, "boto3": { "hashes": [ - "sha256:473438feafe77d29fbea532a91a65de0d8751a4fa5822127218710a205e28e7a", - "sha256:ccb1a365d3084de53b58f8dfc056462f49b16931c139f4c8ac5f0bca8cb8fe81" + "sha256:5ef7166fe5060637b92af8dc152cd7acecf96b3fc9c5456706a886cadb534391", + "sha256:fc8001519c8842e766ad3793bde3fbd0bb39e821a582fc12cf67876b8f3cf7f1" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.73" + "version": "==1.35.78" }, "botocore": { "hashes": [ - "sha256:8a6a0f5ad119e38d850571df8c625dbad66aec1b20c15f84cdcb95258f9f1edb", - "sha256:b2e3ecdd1769f011f72c4c0d0094570ba125f4ca327f24269e4d68eb5d9878b9" + "sha256:41c37bd7c0326f25122f33ec84fb80fc0a14d7fcc9961431b0e57568e88c9cb5", + "sha256:6905036c25449ae8dba5e950e4b908e4b8a6fe6b516bf61e007ecb62fa21f323" ], "markers": "python_version >= '3.8'", - "version": "==1.35.73" + "version": "==1.35.78" }, "duckdb": { "hashes": [ @@ -102,64 +111,64 @@ }, "numpy": { "hashes": [ - "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", - "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", - "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", - "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", - "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", - "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", - "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", - "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", - "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", - "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", - "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", - "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", - "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", - "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", - "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", - "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", - "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", - "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", - "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", - "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", - "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", - "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", - "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", - "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", - "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", - "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", - "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", - "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", - "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", - "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", - "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", - "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", - "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", - "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", - "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", - "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", - "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", - "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", - "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", - "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", - "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", - "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", - "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", - "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", - "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", - "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", - "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", - "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", - "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", - "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", - "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", - "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", - "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", - "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", - "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4" + "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608", + "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef", + "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90", + "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae", + "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83", + "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0", + "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73", + "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671", + "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69", + "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa", + "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066", + "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da", + "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9", + "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e", + "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3", + "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a", + "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74", + "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3", + "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410", + "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72", + "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d", + "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4", + "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038", + "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e", + "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13", + "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d", + "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95", + "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31", + "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3", + "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03", + "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6", + "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2", + "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b", + "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7", + "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab", + "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219", + "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571", + "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d", + "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1", + "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca", + "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661", + "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e", + "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e", + "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e", + "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a", + "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3", + "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881", + "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221", + "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742", + "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773", + "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e", + "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529", + "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67", + "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c", + "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367" ], "markers": "python_version >= '3.12'", - "version": "==2.1.3" + "version": "==2.2.0" }, "pandas": { "hashes": [ @@ -284,11 +293,11 @@ }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "version": "==1.17.0" }, "tzdata": { "hashes": [ @@ -347,39 +356,39 @@ }, "boto3": { "hashes": [ - "sha256:473438feafe77d29fbea532a91a65de0d8751a4fa5822127218710a205e28e7a", - "sha256:ccb1a365d3084de53b58f8dfc056462f49b16931c139f4c8ac5f0bca8cb8fe81" + "sha256:5ef7166fe5060637b92af8dc152cd7acecf96b3fc9c5456706a886cadb534391", + "sha256:fc8001519c8842e766ad3793bde3fbd0bb39e821a582fc12cf67876b8f3cf7f1" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.73" + "version": "==1.35.78" }, "boto3-stubs": { "extras": [ "s3" ], "hashes": [ - "sha256:b935f0b62be1e18445f63cd9f5bbb4fe9a792d99efa9eb7f37b641ed4a6e70e0", - "sha256:d1c072dfa59fbe0d91ba8e8966e844d9eb79ccc5f59e49914f796f29cd96a14d" + "sha256:5d023cf1fcc723dfdba29653e0ad9b9933985c813a25bc21807e21eab81e21b4", + "sha256:bb7824c09cbf868940b8d500e7f4ca99cb7e074c0f86771f832ce15c33a313bd" ], "markers": "python_version >= '3.8'", - "version": "==1.35.73" + "version": "==1.35.78" }, "botocore": { "hashes": [ - "sha256:8a6a0f5ad119e38d850571df8c625dbad66aec1b20c15f84cdcb95258f9f1edb", - "sha256:b2e3ecdd1769f011f72c4c0d0094570ba125f4ca327f24269e4d68eb5d9878b9" + "sha256:41c37bd7c0326f25122f33ec84fb80fc0a14d7fcc9961431b0e57568e88c9cb5", + "sha256:6905036c25449ae8dba5e950e4b908e4b8a6fe6b516bf61e007ecb62fa21f323" ], "markers": "python_version >= '3.8'", - "version": "==1.35.73" + "version": "==1.35.78" }, "botocore-stubs": { "hashes": [ - "sha256:54f7bcc325382050ae6aa839163f93f5c4e777db9c0fd2da3ad0744720895fbe", - "sha256:e9a20b0a29621674b46225fdb88bf00a0bca5216413d717895b75ba2dd63c6cc" + "sha256:4cb5c1fca33048a2afca2002719a8d696f7051ab4f0ef5f5ee96df7aaf76a055", + "sha256:86d11b64a72c25766d551a2fedcc93e374d3c9d27aea11a7516af1d357e09637" ], "markers": "python_version >= '3.8'", - "version": "==1.35.73" + "version": "==1.35.78" }, "certifi": { "hashes": [ @@ -594,71 +603,71 @@ "toml" ], "hashes": [ - "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", - "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", - "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", - "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638", - "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4", - "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc", - "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed", - "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a", - "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d", - "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649", - "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c", - "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", - "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", - "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443", - "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", - "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee", - "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", - "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e", - "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", - "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0", - "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb", - "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", - "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb", - "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787", - "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1", - "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", - "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", - "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", - "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", - "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", - "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf", - "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6", - "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", - "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", - "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4", - "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c", - "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8", - "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4", - "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", - "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", - "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea", - "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", - "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", - "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", - "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", - "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50", - "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779", - "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", - "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", - "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", - "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", - "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d", - "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94", - "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", - "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", - "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331", - "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a", - "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0", - "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee", - "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92", - "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", - "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "markers": "python_version >= '3.9'", - "version": "==7.6.8" + "version": "==7.6.9" }, "coveralls": { "hashes": [ @@ -676,7 +685,6 @@ "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", - "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385", "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", @@ -684,7 +692,6 @@ "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", - "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba", "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", @@ -923,10 +930,10 @@ }, "mypy-boto3-s3": { "hashes": [ - "sha256:571e659c1d355499d5e5070f33e613a1e251e6f5d2a57d535c5eaef52ebb6a86", - "sha256:b2a18ca57079659eb602dcfc4abb56425c793ccb1939826e401d4f2ddf9128b0" + "sha256:34ac4cacf8acdafa6e71a2810116b2546376f241761f9eec6ac5a9887309372b", + "sha256:fd4a8734c3bb5a2da52e22258b1836a14aa3460816df25c831790e464334021f" ], - "version": "==1.35.72" + "version": "==1.35.76.post1" }, "mypy-extensions": { "hashes": [ @@ -946,64 +953,64 @@ }, "numpy": { "hashes": [ - "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", - "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", - "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", - "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", - "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", - "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", - "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", - "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", - "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", - "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", - "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", - "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", - "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", - "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", - "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", - "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", - "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", - "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", - "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", - "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", - "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", - "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", - "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", - "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", - "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", - "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", - "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", - "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", - "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", - "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", - "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", - "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", - "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", - "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", - "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", - "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", - "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", - "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", - "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", - "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", - "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", - "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", - "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", - "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", - "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", - "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", - "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", - "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", - "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", - "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", - "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", - "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", - "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", - "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", - "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4" + "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608", + "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef", + "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90", + "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae", + "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83", + "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0", + "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73", + "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671", + "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69", + "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa", + "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066", + "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da", + "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9", + "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e", + "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3", + "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a", + "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74", + "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3", + "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410", + "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72", + "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d", + "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4", + "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038", + "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e", + "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13", + "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d", + "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95", + "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31", + "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3", + "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03", + "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6", + "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2", + "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b", + "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7", + "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab", + "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219", + "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571", + "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d", + "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1", + "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca", + "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661", + "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e", + "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e", + "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e", + "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a", + "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3", + "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881", + "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221", + "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742", + "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773", + "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e", + "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529", + "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67", + "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c", + "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367" ], "markers": "python_version >= '3.12'", - "version": "==2.1.3" + "version": "==2.2.0" }, "packaging": { "hashes": [ @@ -1144,12 +1151,12 @@ }, "pyarrow-stubs": { "hashes": [ - "sha256:6f4a32a14dd5526851830cfb9e91223019d46c192c15ae8d951f762114b73c1e", - "sha256:d58d85d299dc2b1f16994c1fcf2e0f3ab28ec211876233756b9f6534e43a2b39" + "sha256:624b8f47ed207075faeb7a475d87178145adbd709d8feae93d24574b97d9cb42", + "sha256:af9433bf1daa27df8e5a6364d5fe7db9feeb203394686c0f01821d52bc571ee0" ], "index": "pypi", "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==17.12" + "version": "==17.13" }, "pycparser": { "hashes": [ @@ -1270,28 +1277,28 @@ }, "ruff": { "hashes": [ - "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", - "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", - "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", - "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", - "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", - "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", - "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", - "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", - "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", - "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", - "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", - "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", - "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", - "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", - "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", - "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", - "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", - "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5" + "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f", + "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea", + "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248", + "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d", + "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f", + "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29", + "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22", + "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0", + "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1", + "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58", + "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5", + "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d", + "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897", + "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa", + "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93", + "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5", + "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c", + "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.8.1" + "version": "==0.8.2" }, "s3transfer": { "hashes": [ @@ -1312,11 +1319,11 @@ }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "version": "==1.17.0" }, "stack-data": { "hashes": [ @@ -1335,11 +1342,20 @@ }, "types-awscrt": { "hashes": [ - "sha256:0d362a5d62d68ca4216f458172f41c1123ec04791d68364de8ee8b61b528b262", - "sha256:a20b425dabb258bc3d07a5e7de503fd9558dd1542d72de796e74e402c6d493b2" + "sha256:b1b9bb10f337e3fe8f5f508860eb354d9fe093f02e1485955a9e0bdd4e250074", + "sha256:eeb4bd596100927704c8b9f964ec8a246be4943d546f3fd2a8efdddebea422ea" ], "markers": "python_version >= '3.8'", - "version": "==0.23.1" + "version": "==0.23.4" + }, + "types-python-dateutil": { + "hashes": [ + "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", + "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.9.0.20241206" }, "types-pytz": { "hashes": [ diff --git a/tests/test_dataset_write.py b/tests/test_dataset_write.py index d35369d..f8c1e2d 100644 --- a/tests/test_dataset_write.py +++ b/tests/test_dataset_write.py @@ -1,8 +1,8 @@ # ruff: noqa: S105, S106, SLF001, PLR2004, PD901, D209, D205 - import math import os import re +from datetime import UTC, datetime from unittest.mock import patch import pyarrow.dataset as ds @@ -15,7 +15,6 @@ DatasetNotLoadedError, TIMDEXDataset, ) -from timdex_dataset_api.exceptions import InvalidDatasetRecordError from timdex_dataset_api.record import DatasetRecord @@ -29,11 +28,14 @@ def test_dataset_record_init(): "run_type": "full", "action": "index", "run_id": "000-111-aaa-bbb", - "year": 2024, - "month": 12, - "day": 1, } - assert DatasetRecord(**values) + record = DatasetRecord(**values) + assert record + assert (record.year, record.month, record.day) == ( + "2024", + "12", + "01", + ) def test_dataset_record_init_with_invalid_run_date_raise_error(): @@ -46,15 +48,9 @@ def test_dataset_record_init_with_invalid_run_date_raise_error(): "run_type": "full", "action": "index", "run_id": "000-111-aaa-bbb", - "year": None, - "month": None, - "day": None, } with pytest.raises( - InvalidDatasetRecordError, - match=re.escape( - "Cannot parse partition values [year, month, date] from invalid 'run-date' string." # noqa: E501 - ), + ValueError, match=re.escape("time data '-12-01' does not match format '%Y-%m-%d'") ): DatasetRecord(**values) @@ -69,12 +65,21 @@ def test_dataset_record_serialization(): "run_type": "full", "action": "index", "run_id": "abc123", + } + dataset_record = DatasetRecord(**values) + assert dataset_record.to_dict() == { + "timdex_record_id": "alma:123", + "source_record": b"Hello World.", + "transformed_record": b"""{"title":["Hello World."]}""", + "source": "libguides", + "run_date": datetime(2024, 12, 1).astimezone(UTC), + "run_type": "full", + "action": "index", + "run_id": "abc123", "year": "2024", "month": "12", "day": "01", } - dataset_record = DatasetRecord(**values) - assert dataset_record.to_dict() == values def test_dataset_write_records_to_new_dataset(new_dataset, sample_records_iter): diff --git a/tests/utils.py b/tests/utils.py index 68853f9..fc8a420 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -17,9 +17,6 @@ def generate_sample_records( run_type: str | None = "daily", action: str | None = "index", run_id: str | None = None, - year: str | int | None = "2024", - month: str | int | None = "12", - day: str | int | None = "1", ) -> Iterator[DatasetRecord]: """Generate sample DatasetRecords.""" if not run_id: @@ -35,9 +32,6 @@ def generate_sample_records( run_type=run_type, action=action, run_id=run_id, - year=year, - month=month, - day=day, ) diff --git a/timdex_dataset_api/record.py b/timdex_dataset_api/record.py index 95531b4..96b5a38 100644 --- a/timdex_dataset_api/record.py +++ b/timdex_dataset_api/record.py @@ -1,12 +1,15 @@ """timdex_dataset_api/record.py""" -import datetime -from dataclasses import asdict, dataclass +from datetime import UTC, datetime -from timdex_dataset_api.exceptions import InvalidDatasetRecordError +from attrs import asdict, define, field -@dataclass +def strict_date_parse(date_string: str) -> datetime: + return datetime.strptime(date_string, "%Y-%m-%d").astimezone(UTC) + + +@define class DatasetRecord: """Container for single dataset record. @@ -16,56 +19,34 @@ class DatasetRecord: """ # primary columns - timdex_record_id: str - source_record: bytes - transformed_record: bytes - source: str - run_date: str | datetime.datetime - run_type: str - run_id: str - action: str - - # partition columns - year: str | None = None - month: str | None = None - day: str | None = None - - def __post_init__(self) -> None: - """Post init method to derive partition values from self.run_date""" - run_date = self.run_date - - if isinstance(run_date, str): - try: - run_date = datetime.datetime.strptime(run_date, "%Y-%m-%d").astimezone( - datetime.UTC - ) - except ValueError as exception: - raise InvalidDatasetRecordError( - "Cannot parse partition values [year, month, date] from invalid 'run-date' string." # noqa: E501 - ) from exception - - self.year = run_date.strftime("%Y") - self.month = run_date.strftime("%m") - self.day = run_date.strftime("%d") + timdex_record_id: str = field() + source_record: bytes = field() + transformed_record: bytes = field() + source: str = field() + run_date: datetime = field(converter=strict_date_parse) + run_type: str = field() + run_id: str = field() + action: str = field() + + @property + def year(self) -> str: + return self.run_date.strftime("%Y") + + @property + def month(self) -> str: + return self.run_date.strftime("%m") + + @property + def day(self) -> str: + return self.run_date.strftime("%d") def to_dict( self, - *, - validate: bool = True, ) -> dict: """Serialize instance as dictionary.""" - if validate: - self.validate() - - return asdict(self) - - def validate(self) -> None: - """Validate DatasetRecord for writing.""" - # ensure all partition columns are set - missing_partition_values = [ - field for field in ["year", "month", "day"] if getattr(self, field) is None - ] - if missing_partition_values: - raise InvalidDatasetRecordError( - f"Partition values are missing: {', '.join(missing_partition_values)}" - ) + return { + **asdict(self), + "year": self.year, + "month": self.month, + "day": self.day, + } From 8a30ca3571155dccb14b8875e36e8306966cf31e Mon Sep 17 00:00:00 2001 From: jonavellecuerdo Date: Wed, 11 Dec 2024 15:13:34 -0500 Subject: [PATCH 4/4] Update package version number --- timdex_dataset_api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timdex_dataset_api/__init__.py b/timdex_dataset_api/__init__.py index a8d3cad..059018f 100644 --- a/timdex_dataset_api/__init__.py +++ b/timdex_dataset_api/__init__.py @@ -3,7 +3,7 @@ from timdex_dataset_api.dataset import TIMDEXDataset from timdex_dataset_api.record import DatasetRecord -__version__ = "0.2.0" +__version__ = "0.3.0" __all__ = [ "DatasetRecord",