diff --git a/examples/add_metric_data_arrow_example.py b/examples/add_metric_data_arrow_example.py new file mode 100644 index 0000000..e1cb1a1 --- /dev/null +++ b/examples/add_metric_data_arrow_example.py @@ -0,0 +1,38 @@ +from cvec import CVec +from cvec.models.metric import MetricDataPoint +from datetime import datetime, timezone +import random +import os + + +def main() -> None: + cvec = CVec( + host=os.environ.get("CVEC_HOST", "https://your-subdomain.cvector.dev"), + email=os.environ.get("CVEC_EMAIL", "your-email@cvector.app"), + password=os.environ.get("CVEC_PASSWORD", "your-password"), + publishable_key=os.environ.get( + "CVEC_PUBLISHABLE_KEY", "your-cvec-publishable-key" + ), + ) + test_metric_name = "python-sdk/test" + print("\nAdding new metric data using Arrow...") + new_data = [ + MetricDataPoint( + name=test_metric_name, + time=datetime.now(timezone.utc), + value_double=random.random() * 100.0, + value_string=None, + ), + MetricDataPoint( + name=test_metric_name, + time=datetime.now(timezone.utc), + value_double=random.random() * 100.0, + value_string=None, + ), + ] + cvec.add_metric_data(new_data, use_arrow=True) + print("Data added successfully") + + +if __name__ == "__main__": + main() diff --git a/examples/add_metric_data_example.py b/examples/add_metric_data_example.py new file mode 100644 index 0000000..e81fe42 --- /dev/null +++ b/examples/add_metric_data_example.py @@ -0,0 +1,38 @@ +from cvec import CVec +from cvec.models.metric import MetricDataPoint +from datetime import datetime, timezone +import random +import os + + +def main() -> None: + cvec = CVec( + host=os.environ.get("CVEC_HOST", "https://your-subdomain.cvector.dev"), + email=os.environ.get("CVEC_EMAIL", "your-email@cvector.app"), + password=os.environ.get("CVEC_PASSWORD", "your-password"), + publishable_key=os.environ.get( + "CVEC_PUBLISHABLE_KEY", "your-cvec-publishable-key" + ), + ) + test_metric_name = "python-sdk/test" + print("\nAdding new metric data...") + new_data = [ + MetricDataPoint( + name=test_metric_name, + time=datetime.now(timezone.utc), + value_double=random.random() * 100.0, + value_string=None, + ), + MetricDataPoint( + name=test_metric_name, + time=datetime.now(timezone.utc), + value_double=random.random() * 100.0, + value_string=None, + ), + ] + cvec.add_metric_data(new_data, use_arrow=False) + print("Data added successfully") + + +if __name__ == "__main__": + main() diff --git a/examples/get_metric_arrow_example.py b/examples/get_metric_arrow_example.py new file mode 100644 index 0000000..7a5bb52 --- /dev/null +++ b/examples/get_metric_arrow_example.py @@ -0,0 +1,30 @@ +from cvec import CVec +import io +import pyarrow.ipc as ipc # type: ignore[import-untyped] +import os + + +def main() -> None: + cvec = CVec( + host=os.environ.get("CVEC_HOST", "https://your-subdomain.cvector.dev"), + email=os.environ.get("CVEC_EMAIL", "your-email@cvector.app"), + password=os.environ.get("CVEC_PASSWORD", "your-password"), + publishable_key=os.environ.get( + "CVEC_PUBLISHABLE_KEY", "your-cvec-publishable-key" + ), + ) + test_metric_name = "python-sdk/test" + print("\nGetting metric data as Arrow...") + arrow_data = cvec.get_metric_arrow(names=[test_metric_name]) + reader = ipc.open_file(io.BytesIO(arrow_data)) + table = reader.read_all() + print(f"Arrow table shape: {len(table)} rows") + print("\nFirst few rows:") + for i in range(min(5, len(table))): + print( + f"- {table['name'][i].as_py()}: {table['value_double'][i].as_py() or table['value_string'][i].as_py()} at {table['time'][i].as_py()}" + ) + + +if __name__ == "__main__": + main() diff --git a/examples/get_metric_data_objects_example.py b/examples/get_metric_data_objects_example.py new file mode 100644 index 0000000..8fabf05 --- /dev/null +++ b/examples/get_metric_data_objects_example.py @@ -0,0 +1,25 @@ +from cvec import CVec +import os + + +def main() -> None: + cvec = CVec( + host=os.environ.get("CVEC_HOST", "https://your-subdomain.cvector.dev"), + email=os.environ.get("CVEC_EMAIL", "your-email@cvector.app"), + password=os.environ.get("CVEC_PASSWORD", "your-password"), + publishable_key=os.environ.get( + "CVEC_PUBLISHABLE_KEY", "your-cvec-publishable-key" + ), + ) + test_metric_name = "python-sdk/test" + print("\nGetting metric data as objects...") + data_points = cvec.get_metric_data(names=[test_metric_name]) + print(f"Found {len(data_points)} data points") + for point in data_points[:3]: + print( + f"- {point.name}: {point.value_double or point.value_string} at {point.time}" + ) + + +if __name__ == "__main__": + main() diff --git a/examples/get_metrics_example.py b/examples/get_metrics_example.py new file mode 100644 index 0000000..62a6478 --- /dev/null +++ b/examples/get_metrics_example.py @@ -0,0 +1,24 @@ +from cvec import CVec +import os + + +def main() -> None: + cvec = CVec( + host=os.environ.get( + "CVEC_HOST", "https://your-subdomain.cvector.dev" + ), # Replace with your API host + email=os.environ.get("CVEC_EMAIL", "your-email@cvector.app"), + password=os.environ.get("CVEC_PASSWORD", "your-password"), + publishable_key=os.environ.get( + "CVEC_PUBLISHABLE_KEY", "your-cvec-publishable-key" + ), + ) + print("\nGetting available metrics...") + metrics = cvec.get_metrics() + print(f"Found {len(metrics)} metrics") + for metric in metrics: + print(f"- {metric.name}") + + +if __name__ == "__main__": + main() diff --git a/examples/get_spans_example.py b/examples/get_spans_example.py new file mode 100644 index 0000000..3e4c0dd --- /dev/null +++ b/examples/get_spans_example.py @@ -0,0 +1,29 @@ +from cvec import CVec +import os + + +def main() -> None: + cvec = CVec( + host=os.environ.get("CVEC_HOST", "https://your-subdomain.cvector.dev"), + email=os.environ.get("CVEC_EMAIL", "your-email@cvector.app"), + password=os.environ.get("CVEC_PASSWORD", "your-password"), + publishable_key=os.environ.get( + "CVEC_PUBLISHABLE_KEY", "your-cvec-publishable-key" + ), + ) + metrics = cvec.get_metrics() + if metrics: + metric_name = metrics[0].name + print(f"\nGetting spans for metric '{metric_name}'...") + spans = cvec.get_spans(metric_name, limit=5) + print(f"Found {len(spans)} spans") + for span in spans: + print( + f"- Value: {span.value} from {span.raw_start_at} to {span.raw_end_at}" + ) + else: + print("No metrics found to get spans.") + + +if __name__ == "__main__": + main() diff --git a/poetry.lock b/poetry.lock index 2cd0855..4414d06 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,131 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, + {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -32,6 +158,21 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "iniconfig" version = "2.1.0" @@ -46,48 +187,49 @@ files = [ [[package]] name = "mypy" -version = "1.15.0" +version = "1.16.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, - {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, - {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, - {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, - {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, - {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, - {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, - {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, - {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, - {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, - {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, - {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, - {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, - {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, + {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, + {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, + {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, + {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, + {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, + {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, + {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, + {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, + {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, + {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, + {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, + {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, ] [package.dependencies] mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing_extensions = ">=4.6.0" @@ -110,71 +252,6 @@ files = [ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] -[[package]] -name = "numpy" -version = "2.2.5" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26"}, - {file = "numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a"}, - {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19f4718c9012e3baea91a7dba661dcab2451cda2550678dc30d53acb91a7290f"}, - {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:eb7fd5b184e5d277afa9ec0ad5e4eb562ecff541e7f60e69ee69c8d59e9aeaba"}, - {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6413d48a9be53e183eb06495d8e3b006ef8f87c324af68241bbe7a39e8ff54c3"}, - {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7451f92eddf8503c9b8aa4fe6aa7e87fd51a29c2cfc5f7dbd72efde6c65acf57"}, - {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0bcb1d057b7571334139129b7f941588f69ce7c4ed15a9d6162b2ea54ded700c"}, - {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36ab5b23915887543441efd0417e6a3baa08634308894316f446027611b53bf1"}, - {file = "numpy-2.2.5-cp310-cp310-win32.whl", hash = "sha256:422cc684f17bc963da5f59a31530b3936f57c95a29743056ef7a7903a5dbdf88"}, - {file = "numpy-2.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:e4f0b035d9d0ed519c813ee23e0a733db81ec37d2e9503afbb6e54ccfdee0fa7"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d"}, - {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54"}, - {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610"}, - {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b"}, - {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be"}, - {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906"}, - {file = "numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175"}, - {file = "numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e"}, - {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa"}, - {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571"}, - {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073"}, - {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8"}, - {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae"}, - {file = "numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb"}, - {file = "numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9"}, - {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191"}, - {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372"}, - {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d"}, - {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7"}, - {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73"}, - {file = "numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b"}, - {file = "numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133"}, - {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376"}, - {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19"}, - {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0"}, - {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a"}, - {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066"}, - {file = "numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e"}, - {file = "numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4ea7e1cff6784e58fe281ce7e7f05036b3e1c89c6f922a6bfbc0a7e8768adbe"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d7543263084a85fbc09c704b515395398d31d6395518446237eac219eab9e55e"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0255732338c4fdd00996c0421884ea8a3651eea555c3a56b84892b66f696eb70"}, - {file = "numpy-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2e3bdadaba0e040d1e7ab39db73e0afe2c74ae277f5614dad53eadbecbbb169"}, - {file = "numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291"}, -] - [[package]] name = "packaging" version = "25.0" @@ -188,236 +265,309 @@ files = [ ] [[package]] -name = "pandas" -version = "2.2.3" -description = "Powerful data structures for data analysis, time series, and statistics" +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, - {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, - {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, - {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, - {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, - {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, - {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, - {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[package.dependencies] -numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" [package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] -name = "pandas-stubs" -version = "2.2.3.250308" -description = "Type annotations for pandas" +name = "pyarrow" +version = "19.0.1" +description = "Python library for Apache Arrow" optional = false -python-versions = ">=3.10" -groups = ["dev"] +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pandas_stubs-2.2.3.250308-py3-none-any.whl", hash = "sha256:a377edff3b61f8b268c82499fdbe7c00fdeed13235b8b71d6a1dc347aeddc74d"}, - {file = "pandas_stubs-2.2.3.250308.tar.gz", hash = "sha256:3a6e9daf161f00b85c83772ed3d5cff9522028f07a94817472c07b91f46710fd"}, + {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fc28912a2dc924dddc2087679cc8b7263accc71b9ff025a1362b004711661a69"}, + {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fca15aabbe9b8355800d923cc2e82c8ef514af321e18b437c3d782aa884eaeec"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad76aef7f5f7e4a757fddcdcf010a8290958f09e3470ea458c80d26f4316ae89"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d03c9d6f2a3dffbd62671ca070f13fc527bb1867b4ec2b98c7eeed381d4f389a"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:65cf9feebab489b19cdfcfe4aa82f62147218558d8d3f0fc1e9dea0ab8e7905a"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:41f9706fbe505e0abc10e84bf3a906a1338905cbbcf1177b71486b03e6ea6608"}, + {file = "pyarrow-19.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6cb2335a411b713fdf1e82a752162f72d4a7b5dbc588e32aa18383318b05866"}, + {file = "pyarrow-19.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc55d71898ea30dc95900297d191377caba257612f384207fe9f8293b5850f90"}, + {file = "pyarrow-19.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:7a544ec12de66769612b2d6988c36adc96fb9767ecc8ee0a4d270b10b1c51e00"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0148bb4fc158bfbc3d6dfe5001d93ebeed253793fff4435167f6ce1dc4bddeae"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f24faab6ed18f216a37870d8c5623f9c044566d75ec586ef884e13a02a9d62c5"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4982f8e2b7afd6dae8608d70ba5bd91699077323f812a0448d8b7abdff6cb5d3"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49a3aecb62c1be1d822f8bf629226d4a96418228a42f5b40835c1f10d42e4db6"}, + {file = "pyarrow-19.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466"}, + {file = "pyarrow-19.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:80b2ad2b193e7d19e81008a96e313fbd53157945c7be9ac65f44f8937a55427b"}, + {file = "pyarrow-19.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:ee8dec072569f43835932a3b10c55973593abc00936c202707a4ad06af7cb294"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d5d1ec7ec5324b98887bdc006f4d2ce534e10e60f7ad995e7875ffa0ff9cb14"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ad4c0eb4e2a9aeb990af6c09e6fa0b195c8c0e7b272ecc8d4d2b6574809d34"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d383591f3dcbe545f6cc62daaef9c7cdfe0dff0fb9e1c8121101cabe9098cfa6"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b4c4156a625f1e35d6c0b2132635a237708944eb41df5fbe7d50f20d20c17832"}, + {file = "pyarrow-19.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bd1618ae5e5476b7654c7b55a6364ae87686d4724538c24185bbb2952679960"}, + {file = "pyarrow-19.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e45274b20e524ae5c39d7fc1ca2aa923aab494776d2d4b316b49ec7572ca324c"}, + {file = "pyarrow-19.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d9dedeaf19097a143ed6da37f04f4051aba353c95ef507764d344229b2b740ae"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebfb5171bb5f4a52319344ebbbecc731af3f021e49318c74f33d520d31ae0c4"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a21d39fbdb948857f67eacb5bbaaf36802de044ec36fbef7a1c8f0dd3a4ab2"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:99bc1bec6d234359743b01e70d4310d0ab240c3d6b0da7e2a93663b0158616f6"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1b93ef2c93e77c442c979b0d596af45e4665d8b96da598db145b0fec014b9136"}, + {file = "pyarrow-19.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9d46e06846a41ba906ab25302cf0fd522f81aa2a85a71021826f34639ad31ef"}, + {file = "pyarrow-19.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c0fe3dbbf054a00d1f162fda94ce236a899ca01123a798c561ba307ca38af5f0"}, + {file = "pyarrow-19.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:96606c3ba57944d128e8a8399da4812f56c7f61de8c647e3470b417f795d0ef9"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f04d49a6b64cf24719c080b3c2029a3a5b16417fd5fd7c4041f94233af732f3"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a9137cf7e1640dce4c190551ee69d478f7121b5c6f323553b319cac936395f6"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7c1bca1897c28013db5e4c83944a2ab53231f541b9e0c3f4791206d0c0de389a"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:58d9397b2e273ef76264b45531e9d552d8ec8a6688b7390b5be44c02a37aade8"}, + {file = "pyarrow-19.0.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:b9766a47a9cb56fefe95cb27f535038b5a195707a08bf61b180e642324963b46"}, + {file = "pyarrow-19.0.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:6c5941c1aac89a6c2f2b16cd64fe76bcdb94b2b1e99ca6459de4e6f07638d755"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd44d66093a239358d07c42a91eebf5015aa54fccba959db899f932218ac9cc8"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:335d170e050bcc7da867a1ed8ffb8b44c57aaa6e0843b156a501298657b1e972"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:1c7556165bd38cf0cd992df2636f8bcdd2d4b26916c6b7e646101aff3c16f76f"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:699799f9c80bebcf1da0983ba86d7f289c5a2a5c04b945e2f2bcf7e874a91911"}, + {file = "pyarrow-19.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8464c9fbe6d94a7fe1599e7e8965f350fd233532868232ab2596a71586c5a429"}, + {file = "pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e"}, ] -[package.dependencies] -numpy = ">=1.23.5" -types-pytz = ">=2022.1.1" +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] [[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" +name = "pydantic" +version = "2.11.7" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" + [package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] -name = "psycopg" -version = "3.2.9" -description = "PostgreSQL database adapter for Python" +name = "pydantic-core" +version = "2.33.2" +description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6"}, - {file = "psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] [package.dependencies] -typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} -tzdata = {version = "*", markers = "sys_platform == \"win32\""} +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.19.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] [package.extras] -binary = ["psycopg-binary (==3.2.9) ; implementation_name != \"pypy\""] -c = ["psycopg-c (==3.2.9) ; implementation_name != \"pypy\""] -dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "types-shapely (>=2.0)", "wheel (>=0.37)"] -docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] -pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, - {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" pluggy = ">=1.5,<2" +pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" +name = "requests" +version = "2.32.4" +description = "Python HTTP for Humans." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] -six = ">=1.5" +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" -[[package]] -name = "pytz" -version = "2025.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, - {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, -] +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.11.10" +version = "0.11.13" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58"}, - {file = "ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed"}, - {file = "ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca"}, - {file = "ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2"}, - {file = "ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5"}, - {file = "ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641"}, - {file = "ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947"}, - {file = "ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4"}, - {file = "ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f"}, - {file = "ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b"}, - {file = "ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2"}, - {file = "ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523"}, - {file = "ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125"}, - {file = "ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad"}, - {file = "ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19"}, - {file = "ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224"}, - {file = "ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1"}, - {file = "ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, + {file = "ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46"}, + {file = "ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48"}, + {file = "ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492"}, + {file = "ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250"}, + {file = "ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3"}, + {file = "ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b"}, + {file = "ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514"}, ] [[package]] @@ -464,43 +614,51 @@ files = [ ] [[package]] -name = "types-pytz" -version = "2025.2.0.20250326" -description = "Typing stubs for pytz" +name = "typing-extensions" +version = "4.14.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ - {file = "types_pytz-2025.2.0.20250326-py3-none-any.whl", hash = "sha256:3c397fd1b845cd2b3adc9398607764ced9e578a98a5d1fbb4a9bc9253edfb162"}, - {file = "types_pytz-2025.2.0.20250326.tar.gz", hash = "sha256:deda02de24f527066fc8d6a19e284ab3f3ae716a42b4adb6b40e75e408c08d36"}, + {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, + {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, ] [[package]] -name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +name = "typing-inspection" +version = "0.4.1" +description = "Runtime typing introspection tools" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, ] -markers = {main = "python_version < \"3.13\""} + +[package.dependencies] +typing-extensions = ">=4.12.0" [[package]] -name = "tzdata" -version = "2025.2" -description = "Provider of IANA time zone data" +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, - {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [metadata] lock-version = "2.1" python-versions = ">=3.10" -content-hash = "88c626506f796301947928a0229fda5f5656dffdd6d3e79a8426a85643e65002" +content-hash = "b286a33795fe9f764f5da9cc1b9569bd5100251997017f8e85ebc67261707f59" diff --git a/pyproject.toml b/pyproject.toml index 8a34276..ed3308a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,25 +1,23 @@ [project] name = "cvec" -version = "0.1.0" +version = "1.0.0" description = "SDK for CVector Energy" -authors = [ - {name = "CVector",email = "support@cvector.energy"} -] +authors = [{ name = "CVector", email = "support@cvector.energy" }] readme = "README.md" requires-python = ">=3.10" dependencies = [ - "pandas (>=2.2.3,<3.0.0)", - "psycopg (>=3.1.0,<4.0.0)" # Assuming a recent version of psycopg3 + "requests (>=2.32.3,<3.0.0)", + "pydantic>=2.7.0,<3.0.0", + "pyarrow>=18.0.0,<20.0.0", ] [tool.poetry] -packages = [{include = "cvec", from = "src"}] +packages = [{ include = "cvec", from = "src" }] [tool.poetry.group.dev.dependencies] pytest = "^8.3.5" mypy = "^1.15.0" -pandas-stubs = "^2.2.3.250308" ruff = "^0.11.10" [build-system] diff --git a/src/cvec/__init__.py b/src/cvec/__init__.py index 30c8e17..e25fa13 100644 --- a/src/cvec/__init__.py +++ b/src/cvec/__init__.py @@ -1,5 +1,3 @@ from .cvec import CVec -from .span import Span -from .metric import Metric -__all__ = ["CVec", "Span", "Metric"] +__all__ = ["CVec"] diff --git a/src/cvec/cvec.py b/src/cvec/cvec.py index 062a143..91a2fc8 100644 --- a/src/cvec/cvec.py +++ b/src/cvec/cvec.py @@ -1,12 +1,16 @@ import os from datetime import datetime -from typing import Any, List, Optional +from typing import Any, List, Optional, Dict +from urllib.parse import urljoin -import pandas as pd -import psycopg +import requests # type: ignore[import-untyped] -from .span import Span -from .metric import Metric +from cvec.models.metric import Metric, MetricDataPoint +from cvec.models.span import Span +from cvec.utils.arrow_converter import ( + arrow_to_metric_data_points, + metric_data_points_to_arrow, +) class CVec: @@ -15,53 +19,116 @@ class CVec: """ host: Optional[str] - tenant: Optional[str] - api_key: Optional[str] default_start_at: Optional[datetime] default_end_at: Optional[datetime] + # Supabase authentication + _access_token: Optional[str] + _refresh_token: Optional[str] + _publishable_key: Optional[str] def __init__( self, host: Optional[str] = None, - tenant: Optional[str] = None, - api_key: Optional[str] = None, default_start_at: Optional[datetime] = None, default_end_at: Optional[datetime] = None, + email: Optional[str] = None, + password: Optional[str] = None, + publishable_key: Optional[str] = None, ) -> None: - """ - Setup the SDK with the given host and API Key. - The host and API key are loaded from environment variables CVEC_HOST, - CVEC_TENANT, CVEC_API_KEY, if they are not given as arguments to the constructor. - The default_start_at and default_end_at can provide a default query time interval for API methods. - """ self.host = host or os.environ.get("CVEC_HOST") - self.tenant = tenant or os.environ.get("CVEC_TENANT") - self.api_key = api_key or os.environ.get("CVEC_API_KEY") self.default_start_at = default_start_at self.default_end_at = default_end_at + # Supabase authentication + self._access_token = None + self._refresh_token = None + self._publishable_key = publishable_key or os.environ.get( + "CVEC_PUBLISHABLE_KEY" + ) + if not self.host: raise ValueError( "CVEC_HOST must be set either as an argument or environment variable" ) - if not self.tenant: + if not self._publishable_key: raise ValueError( - "CVEC_TENANT must be set either as an argument or environment variable" + "CVEC_PUBLISHABLE_KEY must be set either as an argument or environment variable" ) - if not self.api_key: + + # Handle authentication + if email and password: + self._login_with_supabase(email, password) + else: raise ValueError( - "CVEC_API_KEY must be set either as an argument or environment variable" + "Email and password must be provided for Supabase authentication" ) - def _get_db_connection(self) -> psycopg.Connection: - """Helper method to establish a database connection.""" - return psycopg.connect( - user=self.tenant, - password=self.api_key, - host=self.host, - dbname=self.tenant, + def _get_headers(self) -> Dict[str, str]: + """Helper method to get request headers.""" + if not self._access_token: + raise ValueError("No access token available. Please login first.") + + return { + "Authorization": f"Bearer {self._access_token}", + "Content-Type": "application/json", + } + + def _make_request( + self, + method: str, + endpoint: str, + params: Optional[Dict[str, Any]] = None, + json: Optional[Dict[str, Any]] = None, + data: Optional[bytes] = None, + headers: Optional[Dict[str, str]] = None, + ) -> Any: + """Helper method to make HTTP requests.""" + url = urljoin(self.host or "", endpoint) + request_headers = self._get_headers() + if headers: + request_headers.update(headers) + + response = requests.request( + method=method, + url=url, + headers=request_headers, + params=params, + json=json, + data=data, ) + # If we get a 401 and we have Supabase tokens, try to refresh and retry + if response.status_code == 401 and self._access_token and self._refresh_token: + try: + self._refresh_supabase_token() + # Update headers with new token + request_headers = self._get_headers() + if headers: + request_headers.update(headers) + + # Retry the request + response = requests.request( + method=method, + url=url, + headers=request_headers, + params=params, + json=json, + data=data, + ) + except Exception: + print("Token refresh failed") + # If refresh fails, continue with the original error + pass + + response.raise_for_status() + + if ( + response.headers.get("content-type") + == "application/vnd.apache.arrow.stream" + ): + return response.content + return response.json() + def get_spans( self, name: str, @@ -93,104 +160,82 @@ def get_spans( _start_at = start_at or self.default_start_at _end_at = end_at or self.default_end_at - with self._get_db_connection() as conn: - with conn.cursor() as cur: - query_params = { - "metric": name, - "start_at": _start_at, - "end_at": _end_at, - # Fetch up to 'limit' points. If limit is None, then the `LIMIT NULL` clause - # has no effect (in PostgreSQL). - "limit": limit, - } - - combined_query = """ - SELECT - time, - value_double, - value_string - FROM metric_data - WHERE metric = %(metric)s - AND (time >= %(start_at)s OR %(start_at)s IS NULL) - AND (time < %(end_at)s OR %(end_at)s IS NULL) - ORDER BY time DESC - LIMIT %(limit)s - """ - cur.execute(combined_query, query_params) - db_rows = cur.fetchall() - spans = [] - - # None indicates that the end time is not known; the span extends beyond - # the query period. - raw_end_at = None - for time, value_double, value_string in db_rows: - raw_start_at = time - value = value_double if value_double is not None else value_string - spans.append( - Span( - id=None, - name=name, - value=value, - raw_start_at=raw_start_at, - raw_end_at=raw_end_at, - metadata=None, - ) - ) - raw_end_at = raw_start_at - - return spans + params: Dict[str, Any] = { + "start_at": _start_at.isoformat() if _start_at else None, + "end_at": _end_at.isoformat() if _end_at else None, + "limit": limit, + } + + response_data = self._make_request( + "GET", f"/api/metrics/spans/{name}", params=params + ) + return [Span.model_validate(span_data) for span_data in response_data] def get_metric_data( self, names: Optional[List[str]] = None, start_at: Optional[datetime] = None, end_at: Optional[datetime] = None, - ) -> pd.DataFrame: + use_arrow: bool = False, + ) -> List[MetricDataPoint]: """ Return all data-points within a given [start_at, end_at) interval, optionally selecting a given list of metric names. - The return value is a Pandas DataFrame with four columns: name, time, value_double, value_string. - One row is returned for each metric value transition. + Returns a list of MetricDataPoint objects, one for each metric value transition. + + Args: + names: Optional list of metric names to filter by + start_at: Optional start time for the query + end_at: Optional end time for the query + use_arrow: If True, uses Arrow format for data transfer (more efficient for large datasets) """ _start_at = start_at or self.default_start_at _end_at = end_at or self.default_end_at - params = { - "start_at": _start_at, - "end_at": _end_at, - "tag_names_is_null": names is None, - # Pass an empty tuple if names is None or empty, otherwise the tuple of names. - # ANY(%(empty_tuple)s) will correctly result in no matches if names is empty. - # If names is None, the tag_names_is_null condition handles it. - "tag_names_list": names if names else [], + params: Dict[str, Any] = { + "start_at": _start_at.isoformat() if _start_at else None, + "end_at": _end_at.isoformat() if _end_at else None, + "names": ",".join(names) if names else None, } - sql_query = """ - SELECT metric AS name, time, value_double, value_string - FROM metric_data - WHERE (time >= %(start_at)s OR %(start_at)s IS NULL) - AND (time < %(end_at)s OR %(end_at)s IS NULL) - AND (%(tag_names_is_null)s IS TRUE OR metric = ANY(%(tag_names_list)s)) - ORDER BY name, time ASC - """ + endpoint = "/api/metrics/data/arrow" if use_arrow else "/api/metrics/data" + response_data = self._make_request("GET", endpoint, params=params) - with self._get_db_connection() as conn: - with conn.cursor() as cur: - cur.execute(sql_query, params) - rows = cur.fetchall() + if use_arrow: + return arrow_to_metric_data_points(response_data) + return [ + MetricDataPoint.model_validate(point_data) for point_data in response_data + ] - if not rows: - return pd.DataFrame( - columns=["name", "time", "value_double", "value_string"] - ) + def get_metric_arrow( + self, + names: Optional[List[str]] = None, + start_at: Optional[datetime] = None, + end_at: Optional[datetime] = None, + ) -> bytes: + """ + Return all data-points within a given [start_at, end_at) interval, + optionally selecting a given list of metric names. + Returns Arrow IPC format data that can be read using pyarrow.ipc.open_file. - # Create DataFrame from fetched rows - df = pd.DataFrame( - rows, columns=["name", "time", "value_double", "value_string"] - ) + Args: + names: Optional list of metric names to filter by + start_at: Optional start time for the query + end_at: Optional end time for the query + """ + _start_at = start_at or self.default_start_at + _end_at = end_at or self.default_end_at - # Return the DataFrame with the required columns - return df[["name", "time", "value_double", "value_string"]] + params: Dict[str, Any] = { + "start_at": _start_at.isoformat() if _start_at else None, + "end_at": _end_at.isoformat() if _end_at else None, + "names": ",".join(names) if names else None, + } + + endpoint = "/api/metrics/data/arrow" + result = self._make_request("GET", endpoint, params=params) + assert isinstance(result, bytes) + return result def get_metrics( self, start_at: Optional[datetime] = None, end_at: Optional[datetime] = None @@ -199,44 +244,83 @@ def get_metrics( Return a list of metrics that had at least one transition in the given [start_at, end_at) interval. All metrics are returned if no start_at and end_at are given. """ - sql_query: str - params: Optional[dict[str, Any]] - - if start_at is None and end_at is None: - # No time interval specified by arguments, return all tags - sql_query = """ - SELECT id, normalized_name AS name, birth_at, death_at - FROM tag_names - ORDER BY name ASC; - """ - params = None - else: - # Time interval specified, find tags with transitions in the interval - _start_at = start_at or self.default_start_at - _end_at = end_at or self.default_end_at - - params = {"start_at_param": _start_at, "end_at_param": _end_at} - sql_query = f""" - SELECT DISTINCT metric_id AS id, metric AS name, birth_at, death_at - FROM {self.tenant}.metric_data - WHERE (time >= %(start_at_param)s OR %(start_at_param)s IS NULL) - AND (time < %(end_at_param)s OR %(end_at_param)s IS NULL) - ORDER BY name ASC; - """ - - with self._get_db_connection() as conn: - with conn.cursor() as cur: - cur.execute(sql_query, params) - rows = cur.fetchall() - - # Format rows into list of Metric objects - metrics_list = [ - Metric( - id=row[0], - name=row[1], - birth_at=row[2], - death_at=row[3], + _start_at = start_at or self.default_start_at + _end_at = end_at or self.default_end_at + + params: Dict[str, Any] = { + "start_at": _start_at.isoformat() if _start_at else None, + "end_at": _end_at.isoformat() if _end_at else None, + } + + response_data = self._make_request("GET", "/api/metrics/", params=params) + return [Metric.model_validate(metric_data) for metric_data in response_data] + + def add_metric_data( + self, + data_points: List[MetricDataPoint], + use_arrow: bool = False, + ) -> None: + """ + Add multiple metric data points to the database. + + Args: + data_points: List of MetricDataPoint objects to add + use_arrow: If True, uses Arrow format for data transfer (more efficient for large datasets) + """ + endpoint = "/api/metrics/data/arrow" if use_arrow else "/api/metrics/data" + + if use_arrow: + arrow_data = metric_data_points_to_arrow(data_points) + self._make_request( + "POST", + endpoint, + data=arrow_data, + headers={"Content-Type": "application/vnd.apache.arrow.stream"}, ) - for row in rows - ] - return metrics_list + else: + data_dicts: List[Dict[str, Any]] = [ + point.model_dump(mode="json") for point in data_points + ] + self._make_request("POST", endpoint, json=data_dicts) # type: ignore[arg-type] + + def _login_with_supabase(self, email: str, password: str) -> None: + """ + Login to Supabase and get access/refresh tokens. + + Args: + email: User email + password: User password + """ + supabase_url = f"{self.host}/supabase/auth/v1/token?grant_type=password" + + payload = {"email": email, "password": password} + + headers = {"Content-Type": "application/json", "apikey": self._publishable_key} + + response = requests.post(supabase_url, json=payload, headers=headers) + response.raise_for_status() + + data = response.json() + + self._access_token = data["access_token"] + self._refresh_token = data["refresh_token"] + + def _refresh_supabase_token(self) -> None: + """ + Refresh the Supabase access token using the refresh token. + """ + if not self._refresh_token: + raise ValueError("No refresh token available") + + supabase_url = f"{self.host}/supabase/auth/v1/token?grant_type=refresh_token" + + payload = {"refresh_token": self._refresh_token} + + headers = {"Content-Type": "application/json", "apikey": self._publishable_key} + + response = requests.post(supabase_url, json=payload, headers=headers) + response.raise_for_status() + + data = response.json() + self._access_token = data["access_token"] + self._refresh_token = data["refresh_token"] diff --git a/src/cvec/metric.py b/src/cvec/metric.py deleted file mode 100644 index 5a392b8..0000000 --- a/src/cvec/metric.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime -from typing import Optional - - -class Metric: - """ - Represents metadata for a metric. - """ - - id: int - name: str - birth_at: Optional[datetime] - death_at: Optional[datetime] - - def __init__( - self, - id: int, - name: str, - birth_at: Optional[datetime], - death_at: Optional[datetime], - ): - self.id = id - self.name = name - self.birth_at = birth_at - self.death_at = death_at - - def __repr__(self) -> str: - return ( - f"Metric(id={self.id!r}, name={self.name!r}, " - f"birth_at={self.birth_at!r}, death_at={self.death_at!r})" - ) diff --git a/src/cvec/models/__init__.py b/src/cvec/models/__init__.py new file mode 100644 index 0000000..d986a8d --- /dev/null +++ b/src/cvec/models/__init__.py @@ -0,0 +1,4 @@ +from .metric import Metric, MetricDataPoint +from .span import Span + +__all__ = ["Metric", "MetricDataPoint", "Span"] diff --git a/src/cvec/models/metric.py b/src/cvec/models/metric.py new file mode 100644 index 0000000..2154e2b --- /dev/null +++ b/src/cvec/models/metric.py @@ -0,0 +1,30 @@ +from datetime import datetime +from typing import Optional + +from pydantic import BaseModel, ConfigDict + + +class MetricDataPoint(BaseModel): + """ + Represents a single data point for a metric. + """ + + name: str + time: datetime + value_double: Optional[float] = None + value_string: Optional[str] = None + + model_config = ConfigDict(json_encoders={datetime: lambda dt: dt.isoformat()}) + + +class Metric(BaseModel): + """ + Represents metadata for a metric. + """ + + id: int + name: str + birth_at: Optional[datetime] = None + death_at: Optional[datetime] = None + + model_config = ConfigDict(json_encoders={datetime: lambda dt: dt.isoformat()}) diff --git a/src/cvec/models/span.py b/src/cvec/models/span.py new file mode 100644 index 0000000..bdff552 --- /dev/null +++ b/src/cvec/models/span.py @@ -0,0 +1,17 @@ +from datetime import datetime +from typing import Any, Optional, Union + +from pydantic import BaseModel + + +class Span(BaseModel): + """ + Represents a time span where a metric has a constant value. + """ + + id: Optional[Any] = None + name: str + value: Optional[Union[float, str]] + raw_start_at: datetime + raw_end_at: Optional[datetime] + metadata: Optional[Any] = None diff --git a/src/cvec/span.py b/src/cvec/span.py deleted file mode 100644 index 55078bc..0000000 --- a/src/cvec/span.py +++ /dev/null @@ -1,42 +0,0 @@ -from datetime import datetime -from typing import Any, Optional, Union - - -class Span: - """ - Represents a time span where a metric has a constant value. - """ - - id: Optional[Any] - name: str - value: Optional[Union[float, str]] - raw_start_at: datetime - raw_end_at: Optional[datetime] - metadata: Optional[Any] - - def __init__( - self, - id: Optional[Any], - name: str, - value: Optional[Union[float, str]], - raw_start_at: datetime, - raw_end_at: Optional[datetime], - metadata: Optional[Any], - ): - self.id = id - self.name = name - self.value = value - self.raw_start_at = raw_start_at - self.raw_end_at = raw_end_at - self.metadata = metadata - - def __repr__(self) -> str: - raw_start_at_repr = ( - self.raw_start_at.isoformat() if self.raw_start_at else "None" - ) - raw_end_at_repr = self.raw_end_at.isoformat() if self.raw_end_at else "None" - return ( - f"Span(id={self.id!r}, name={self.name!r}, value={self.value!r}, " - f"raw_start_at={raw_start_at_repr}, raw_end_at={raw_end_at_repr}, " - f"metadata={self.metadata!r})" - ) diff --git a/src/cvec/utils/__init__.py b/src/cvec/utils/__init__.py new file mode 100644 index 0000000..6c29dd0 --- /dev/null +++ b/src/cvec/utils/__init__.py @@ -0,0 +1,3 @@ +from .arrow_converter import arrow_to_metric_data_points, metric_data_points_to_arrow + +__all__ = ["arrow_to_metric_data_points", "metric_data_points_to_arrow"] diff --git a/src/cvec/utils/arrow_converter.py b/src/cvec/utils/arrow_converter.py new file mode 100644 index 0000000..89a287c --- /dev/null +++ b/src/cvec/utils/arrow_converter.py @@ -0,0 +1,74 @@ +import io +from typing import List + +import pyarrow as pa # type: ignore[import-untyped] +import pyarrow.ipc as ipc # type: ignore[import-untyped] + +from cvec.models.metric import MetricDataPoint + + +def metric_data_points_to_arrow(data_points: List[MetricDataPoint]) -> bytes: + """ + Convert metric data points to Arrow format. + + Args: + data_points: List of MetricDataPoint objects to convert + + Returns: + bytes: Arrow IPC format data + """ + # Create arrays for each field + names = [point.name for point in data_points] + times = [point.time for point in data_points] + value_doubles = [point.value_double for point in data_points] + value_strings = [point.value_string for point in data_points] + + # Create Arrow arrays + names_array = pa.array(names) + times_array = pa.array(times, type=pa.timestamp("us", tz="UTC")) + value_doubles_array = pa.array(value_doubles) + value_strings_array = pa.array(value_strings) + + # Create Arrow table + table = pa.table( + { + "name": names_array, + "time": times_array, + "value_double": value_doubles_array, + "value_string": value_strings_array, + } + ) + + # Convert to Arrow IPC format + sink = pa.BufferOutputStream() + with ipc.new_file(sink, table.schema) as writer: + writer.write_table(table) + return bytes(sink.getvalue().to_pybytes()) + + +def arrow_to_metric_data_points(arrow_data: bytes) -> List[MetricDataPoint]: + """ + Convert Arrow format to metric data points. + + Args: + arrow_data: Arrow IPC format data + + Returns: + List[MetricDataPoint]: List of converted metric data points + """ + # Read Arrow data + reader = ipc.open_file(io.BytesIO(arrow_data)) + table = reader.read_all() + + # Convert to list of MetricDataPoint + data_points: List[MetricDataPoint] = [] + for i in range(len(table)): + data_points.append( + MetricDataPoint( + name=table["name"][i].as_py(), + time=table["time"][i].as_py(), + value_double=table["value_double"][i].as_py(), + value_string=table["value_string"][i].as_py(), + ) + ) + return data_points diff --git a/tests/test_cvec.py b/tests/test_cvec.py index eb31985..de88528 100644 --- a/tests/test_cvec.py +++ b/tests/test_cvec.py @@ -1,464 +1,362 @@ import pytest import os -from unittest.mock import patch, MagicMock +from unittest.mock import patch from datetime import datetime -import pandas as pd -import numpy as np -from pandas.testing import assert_frame_equal -from cvec import CVec, Metric +from cvec import CVec +from cvec.models.metric import Metric +import pyarrow as pa # type: ignore[import-untyped] +import pyarrow.ipc as ipc # type: ignore[import-untyped] +import io +from typing import Any class TestCVecConstructor: - def test_constructor_with_arguments(self) -> None: + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_constructor_with_arguments(self, mock_login: Any) -> None: """Test CVec constructor with all arguments provided.""" client = CVec( host="test_host", - tenant="test_tenant", - api_key="test_api_key", default_start_at=datetime(2023, 1, 1, 0, 0, 0), default_end_at=datetime(2023, 1, 2, 0, 0, 0), + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", ) assert client.host == "test_host" - assert client.tenant == "test_tenant" - assert client.api_key == "test_api_key" assert client.default_start_at == datetime(2023, 1, 1, 0, 0, 0) assert client.default_end_at == datetime(2023, 1, 2, 0, 0, 0) + assert client._publishable_key == "test_publishable_key" + @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.dict( os.environ, { "CVEC_HOST": "env_host", - "CVEC_TENANT": "env_tenant", - "CVEC_API_KEY": "env_api_key", + "CVEC_PUBLISHABLE_KEY": "env_publishable_key", }, clear=True, ) - def test_constructor_with_env_vars(self) -> None: + def test_constructor_with_env_vars(self, mock_login: Any) -> None: """Test CVec constructor with environment variables.""" client = CVec( default_start_at=datetime(2023, 2, 1, 0, 0, 0), default_end_at=datetime(2023, 2, 2, 0, 0, 0), + email="user@example.com", + password="password123", ) assert client.host == "env_host" - assert client.tenant == "env_tenant" - assert client.api_key == "env_api_key" + assert client._publishable_key == "env_publishable_key" assert client.default_start_at == datetime(2023, 2, 1, 0, 0, 0) assert client.default_end_at == datetime(2023, 2, 2, 0, 0, 0) + @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.dict(os.environ, {}, clear=True) - def test_constructor_missing_host_raises_value_error(self) -> None: + def test_constructor_missing_host_raises_value_error(self, mock_login: Any) -> None: """Test CVec constructor raises ValueError if host is missing.""" with pytest.raises( ValueError, match="CVEC_HOST must be set either as an argument or environment variable", ): - CVec(tenant="test_tenant", api_key="test_api_key") + CVec( + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", + ) + @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.dict(os.environ, {}, clear=True) - def test_constructor_missing_tenant_raises_value_error(self) -> None: - """Test CVec constructor raises ValueError if tenant is missing.""" + def test_constructor_missing_publishable_key_raises_value_error( + self, mock_login: Any + ) -> None: + """Test CVec constructor raises ValueError if publishable_key is missing.""" with pytest.raises( ValueError, - match="CVEC_TENANT must be set either as an argument or environment variable", + match="CVEC_PUBLISHABLE_KEY must be set either as an argument or environment variable", ): - CVec(host="test_host", api_key="test_api_key") + CVec(host="test_host", email="user@example.com", password="password123") + @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.dict(os.environ, {}, clear=True) - def test_constructor_missing_api_key_raises_value_error(self) -> None: - """Test CVec constructor raises ValueError if api_key is missing.""" + def test_constructor_missing_email_password_raises_value_error( + self, mock_login: Any + ) -> None: + """Test CVec constructor raises ValueError if email or password is missing.""" with pytest.raises( ValueError, - match="CVEC_API_KEY must be set either as an argument or environment variable", + match="Email and password must be provided for Supabase authentication", ): - CVec(host="test_host", tenant="test_tenant") + CVec(host="test_host", publishable_key="test_publishable_key") + @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.dict( os.environ, { "CVEC_HOST": "env_host", - # CVEC_TENANT is missing - "CVEC_API_KEY": "env_api_key", + # CVEC_PUBLISHABLE_KEY is missing }, clear=True, ) - def test_constructor_missing_tenant_env_var_raises_value_error(self) -> None: - """Test CVec constructor raises ValueError if CVEC_TENANT env var is missing.""" + def test_constructor_missing_publishable_key_env_var_raises_value_error( + self, mock_login: Any + ) -> None: + """Test CVec constructor raises ValueError if CVEC_PUBLISHABLE_KEY env var is missing.""" with pytest.raises( ValueError, - match="CVEC_TENANT must be set either as an argument or environment variable", + match="CVEC_PUBLISHABLE_KEY must be set either as an argument or environment variable", ): - CVec() + CVec(email="user@example.com", password="password123") - def test_constructor_args_override_env_vars(self) -> None: + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_constructor_args_override_env_vars(self, mock_login: Any) -> None: """Test CVec constructor arguments override environment variables.""" with patch.dict( os.environ, { "CVEC_HOST": "env_host", - "CVEC_TENANT": "env_tenant", - "CVEC_API_KEY": "env_api_key", + "CVEC_PUBLISHABLE_KEY": "env_publishable_key", }, clear=True, ): client = CVec( host="arg_host", - tenant="arg_tenant", - api_key="arg_api_key", default_start_at=datetime(2023, 3, 1, 0, 0, 0), default_end_at=datetime(2023, 3, 2, 0, 0, 0), + email="user@example.com", + password="password123", + publishable_key="arg_publishable_key", ) assert client.host == "arg_host" - assert client.tenant == "arg_tenant" - assert client.api_key == "arg_api_key" + assert client._publishable_key == "arg_publishable_key" assert client.default_start_at == datetime(2023, 3, 1, 0, 0, 0) assert client.default_end_at == datetime(2023, 3, 2, 0, 0, 0) class TestCVecGetSpans: - @patch("cvec.cvec.psycopg.connect") - def test_get_spans_basic_case(self, mock_connect: MagicMock) -> None: - """Test get_spans with a few data points.""" - # Setup mock connection and cursor - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - - # Sample data (time, value_double, value_string) - newest first - time1 = datetime(2023, 1, 1, 10, 0, 0) - time2 = datetime(2023, 1, 1, 11, 0, 0) - time3 = datetime(2023, 1, 1, 12, 0, 0) - mock_db_rows = [ - (time3, 30.0, None), # Newest - (time2, None, "val2"), - (time1, 10.0, None), # Oldest + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_spans_basic_case(self, mock_login: Any) -> None: + # Simulate backend response + response_data = [ + { + "name": "test_tag", + "value": 30.0, + "raw_start_at": datetime(2023, 1, 1, 12, 0, 0), + "raw_end_at": None, + }, + { + "name": "test_tag", + "value": "val2", + "raw_start_at": datetime(2023, 1, 1, 11, 0, 0), + "raw_end_at": datetime(2023, 1, 1, 12, 0, 0), + }, + { + "name": "test_tag", + "value": 10.0, + "raw_start_at": datetime(2023, 1, 1, 10, 0, 0), + "raw_end_at": datetime(2023, 1, 1, 11, 0, 0), + }, ] - mock_cur.fetchall.return_value = mock_db_rows - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") - tag_name = "test_tag" - spans = client.get_spans(name=tag_name) - + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", + ) + client._make_request = lambda *args, **kwargs: response_data # type: ignore[method-assign] + spans = client.get_spans(name="test_tag") assert len(spans) == 3 - mock_cur.execute.assert_called_once() - - # Verify psycopg query parameters - (_sql, params), _kwargs = mock_cur.execute.call_args - assert params["metric"] == tag_name - assert params["end_at"] is None # Default end_at - assert params["limit"] is None # Default limit - - # Span 1 (from newest data point: time3) - # The raw_end_at is None for the newest span, because the span is still open. - assert spans[0].name == tag_name + assert spans[0].name == "test_tag" assert spans[0].value == 30.0 - assert spans[0].raw_start_at == time3 + assert spans[0].raw_start_at == datetime(2023, 1, 1, 12, 0, 0) assert spans[0].raw_end_at is None - - # Span 2 (from data point: time2) - assert spans[1].name == tag_name assert spans[1].value == "val2" - assert spans[1].raw_start_at == time2 - assert spans[1].raw_end_at == time3 - - # Span 3 (from oldest data point: time1) - assert spans[2].name == tag_name assert spans[2].value == 10.0 - assert spans[2].raw_start_at == time1 - assert spans[2].raw_end_at == time2 class TestCVecGetMetrics: - @patch("cvec.cvec.psycopg.connect") - def test_get_metrics_no_interval(self, mock_connect: MagicMock) -> None: - """Test get_metrics when no start_at or end_at is provided (fetches all metrics).""" - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - - time_birth1 = datetime(2023, 1, 1, 0, 0, 0) - time_death1 = datetime(2023, 1, 10, 0, 0, 0) - time_birth2 = datetime(2023, 2, 1, 0, 0, 0) - mock_db_rows = [ - (1, "metric1", time_birth1, time_death1), - (2, "metric2", time_birth2, None), + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_metrics_no_interval(self, mock_login: Any) -> None: + response_data = [ + { + "id": 1, + "name": "metric1", + "birth_at": datetime(2023, 1, 1, 0, 0, 0), + "death_at": datetime(2023, 1, 10, 0, 0, 0), + }, + { + "id": 2, + "name": "metric2", + "birth_at": datetime(2023, 2, 1, 0, 0, 0), + "death_at": None, + }, ] - mock_cur.fetchall.return_value = mock_db_rows - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", + ) + client._make_request = lambda *args, **kwargs: response_data # type: ignore[method-assign] metrics = client.get_metrics() - - mock_cur.execute.assert_called_once() - sql_query, params = mock_cur.execute.call_args.args - assert "SELECT id, normalized_name AS name, birth_at, death_at" in sql_query - assert "FROM tag_names" in sql_query - assert "ORDER BY name ASC" in sql_query - assert params is None # No params when fetching all - assert len(metrics) == 2 assert isinstance(metrics[0], Metric) assert metrics[0].id == 1 assert metrics[0].name == "metric1" - assert metrics[0].birth_at == time_birth1 - assert metrics[0].death_at == time_death1 - - assert isinstance(metrics[1], Metric) assert metrics[1].id == 2 assert metrics[1].name == "metric2" - assert metrics[1].birth_at == time_birth2 - assert metrics[1].death_at is None - - @patch("cvec.cvec.psycopg.connect") - def test_get_metrics_with_interval(self, mock_connect: MagicMock) -> None: - """Test get_metrics when a start_at and end_at interval is provided.""" - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - time_birth1 = datetime(2023, 1, 1, 0, 0, 0) - mock_db_rows = [ - (1, "metric_in_interval", time_birth1, None), + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_metrics_with_interval(self, mock_login: Any) -> None: + response_data = [ + { + "id": 1, + "name": "metric_in_interval", + "birth_at": datetime(2023, 1, 1, 0, 0, 0), + "death_at": None, + }, ] - mock_cur.fetchall.return_value = mock_db_rows - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") - start_query = datetime(2023, 1, 5, 0, 0, 0) - end_query = datetime(2023, 1, 15, 0, 0, 0) - metrics = client.get_metrics(start_at=start_query, end_at=end_query) - - mock_cur.execute.assert_called_once() - sql_query, params = mock_cur.execute.call_args.args - assert ( - "SELECT DISTINCT metric_id AS id, metric AS name, birth_at, death_at" - in sql_query + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", ) - assert f"FROM {client.tenant}.metric_data" in sql_query - assert ( - "WHERE (time >= %(start_at_param)s OR %(start_at_param)s IS NULL)" - in sql_query + client._make_request = lambda *args, **kwargs: response_data # type: ignore[method-assign] + metrics = client.get_metrics( + start_at=datetime(2023, 1, 5, 0, 0, 0), + end_at=datetime(2023, 1, 15, 0, 0, 0), ) - assert "AND (time < %(end_at_param)s OR %(end_at_param)s IS NULL)" in sql_query - assert params is not None - assert params["start_at_param"] == start_query - assert params["end_at_param"] == end_query - assert len(metrics) == 1 - assert isinstance(metrics[0], Metric) - assert metrics[0].id == 1 assert metrics[0].name == "metric_in_interval" - assert metrics[0].birth_at == time_birth1 - assert metrics[0].death_at is None - - @patch("cvec.cvec.psycopg.connect") - def test_get_metrics_no_data_found(self, mock_connect: MagicMock) -> None: - """Test get_metrics when no metrics are found for the given criteria.""" - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - mock_cur.fetchall.return_value = [] # No rows returned - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_metrics_no_data_found(self, mock_login: Any) -> None: + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", + ) + client._make_request = lambda *args, **kwargs: [] # type: ignore[method-assign] metrics = client.get_metrics( start_at=datetime(2024, 1, 1), end_at=datetime(2024, 1, 2) ) - - mock_cur.execute.assert_called_once() assert len(metrics) == 0 class TestCVecGetMetricData: - @patch("cvec.cvec.psycopg.connect") - def test_get_metric_data_basic_case(self, mock_connect: MagicMock) -> None: - """Test get_metric_data with a few data points for multiple tags.""" - # Setup mock connection and cursor - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - - # Sample data (metric, time, value_double, value_string) + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_metric_data_basic_case(self, mock_login: Any) -> None: + # Simulate backend response time1 = datetime(2023, 1, 1, 10, 0, 0) time2 = datetime(2023, 1, 1, 11, 0, 0) time3 = datetime(2023, 1, 1, 12, 0, 0) - mock_db_rows = [ - ("tag1", time1, 10.0, None), - ("tag1", time2, 20.0, None), - ("tag2", time3, None, "val_str"), + response_data = [ + {"name": "tag1", "time": time1, "value_double": 10.0, "value_string": None}, + {"name": "tag1", "time": time2, "value_double": 20.0, "value_string": None}, + { + "name": "tag2", + "time": time3, + "value_double": None, + "value_string": "val_str", + }, ] - mock_cur.fetchall.return_value = mock_db_rows - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") - names_to_query = ["tag1", "tag2"] - df = client.get_metric_data(names=names_to_query) - - mock_cur.execute.assert_called_once() - (_sql, params), _kwargs = mock_cur.execute.call_args - assert params["tag_names_is_null"] is False - assert params["tag_names_list"] == names_to_query - assert params["start_at"] is None # Default start_at - assert params["end_at"] is None # Default end_at - - expected_data = { - "name": ["tag1", "tag1", "tag2"], - "time": [time1, time2, time3], - "value_double": [10.0, 20.0, np.nan], # Use np.nan for missing float - "value_string": [None, None, "val_str"], # Use None for missing string - } - expected_df = pd.DataFrame(expected_data) - - # Ensure correct dtypes for comparison, especially for NA handling - expected_df = expected_df.astype( - {"value_double": "float64", "value_string": "object"} + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", ) - df = df.astype({"value_double": "float64", "value_string": "object"}) - - assert_frame_equal(df, expected_df, check_dtype=True) - - @patch("cvec.cvec.psycopg.connect") - def test_get_metric_data_no_data_points(self, mock_connect: MagicMock) -> None: - """Test get_metric_data when no data points are returned.""" - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - - mock_cur.fetchall.return_value = [] - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") - df = client.get_metric_data(names=["non_existent_tag"]) - - mock_cur.execute.assert_called_once() - expected_df = pd.DataFrame( - columns=["name", "time", "value_double", "value_string"] + client._make_request = lambda *args, **kwargs: response_data # type: ignore[method-assign] + data_points = client.get_metric_data(names=["tag1", "tag2"]) + assert len(data_points) == 3 + assert data_points[0].name == "tag1" + assert data_points[0].time == time1 + assert data_points[0].value_double == 10.0 + assert data_points[0].value_string is None + assert data_points[2].name == "tag2" + assert data_points[2].time == time3 + assert data_points[2].value_double is None + assert data_points[2].value_string == "val_str" + + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_metric_data_no_data_points(self, mock_login: Any) -> None: + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", ) - # Ensure correct dtypes for empty DataFrame comparison - expected_df = expected_df.astype( + client._make_request = lambda *args, **kwargs: [] # type: ignore[method-assign] + data_points = client.get_metric_data(names=["non_existent_tag"]) + assert data_points == [] + + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_metric_arrow_basic_case(self, mock_login: Any) -> None: + # Prepare Arrow table + names = ["tag1", "tag1", "tag2"] + times = [ + datetime(2023, 1, 1, 10, 0, 0), + datetime(2023, 1, 1, 11, 0, 0), + datetime(2023, 1, 1, 12, 0, 0), + ] + value_doubles = [10.0, 20.0, None] + value_strings = [None, None, "val_str"] + table = pa.table( { - "name": "object", - "time": "datetime64[ns]", - "value_double": "float64", - "value_string": "object", + "name": pa.array(names), + "time": pa.array(times, type=pa.timestamp("us", tz=None)), + "value_double": pa.array(value_doubles, type=pa.float64()), + "value_string": pa.array(value_strings, type=pa.string()), } ) - df = df.astype( - { - "name": "object", - "time": "datetime64[ns]", - "value_double": "float64", - "value_string": "object", - } + sink = pa.BufferOutputStream() + with ipc.new_file(sink, table.schema) as writer: + writer.write_table(table) + arrow_bytes = sink.getvalue().to_pybytes() + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", ) - assert_frame_equal(df, expected_df, check_dtype=True) - - @patch("cvec.cvec.psycopg.connect") - def test_get_spans_no_data_points(self, mock_connect: MagicMock) -> None: - """Test get_spans when no data points are returned from the database.""" - # Setup mock connection and cursor - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - - mock_cur.fetchall.return_value = [] # No data points - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") - tag_name = "test_tag_no_data" - spans = client.get_spans(name=tag_name) - - assert len(spans) == 0 - mock_cur.execute.assert_called_once() - - # Verify psycopg query parameters - (_sql, params) = mock_cur.execute.call_args.args - assert params["metric"] == tag_name - assert params["end_at"] is None # Default end_at - assert params["limit"] is None # Default limit - - @patch("cvec.cvec.psycopg.connect") - def test_get_spans_with_limit_parameter(self, mock_connect: MagicMock) -> None: - """Test get_spans when a limit parameter is provided.""" - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - - # Sample data (time, value_double, value_string) - newest first - time1 = datetime(2023, 1, 1, 10, 0, 0) - time2 = datetime(2023, 1, 1, 11, 0, 0) - mock_db_rows = [ - (time2, None, "val2"), # Newest - (time1, 10.0, None), # Oldest + client._make_request = lambda *args, **kwargs: arrow_bytes # type: ignore[method-assign] + result = client.get_metric_arrow(names=["tag1", "tag2"]) + reader = ipc.open_file(io.BytesIO(result)) + result_table = reader.read_all() + assert result_table.num_rows == 3 + assert result_table.column("name").to_pylist() == names + assert result_table.column("value_double").to_pylist() == [10.0, 20.0, None] + assert result_table.column("value_string").to_pylist() == [ + None, + None, + "val_str", ] - mock_cur.fetchall.return_value = mock_db_rows - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") - tag_name = "test_tag_limited" - query_limit = 2 - spans = client.get_spans(name=tag_name, limit=query_limit) - - mock_cur.execute.assert_called_once() - - # Verify psycopg query parameters - (_sql, params), _kwargs = mock_cur.execute.call_args - assert params["metric"] == tag_name - assert params["limit"] == query_limit - - assert len(spans) == 2 - - @patch("cvec.cvec.psycopg.connect") - def test_get_spans_with_end_at_parameter(self, mock_connect: MagicMock) -> None: - """Test get_spans when an end_at parameter is provided.""" - # Setup mock connection and cursor - mock_conn = MagicMock() - mock_cur = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cur - - # Sample data (time, value_double, value_string) - newest first - time1 = datetime(2023, 1, 1, 10, 0, 0) - time2 = datetime(2023, 1, 1, 11, 0, 0) - time3 = datetime(2023, 1, 1, 12, 0, 0) - mock_db_rows = [ - (time3, 30.0, None), # Newest - (time2, None, "val2"), - (time1, 10.0, None), # Oldest - ] - mock_cur.fetchall.return_value = mock_db_rows - - client = CVec(host="test_host", tenant="test_tenant", api_key="test_api_key") - tag_name = "test_tag" - # Provide an end_at time that is after all sample data points - query_end_at = datetime(2023, 1, 1, 13, 0, 0) - spans = client.get_spans(name=tag_name, end_at=query_end_at) - - assert len(spans) == 3 - mock_cur.execute.assert_called_once() - - # Verify psycopg query parameters - (_sql, params), _kwargs = mock_cur.execute.call_args - assert params["metric"] == tag_name - assert params["end_at"] == query_end_at - assert params["limit"] is None # Default limit - - # Span 1 (from newest data point: time3) - # The raw_end_at is None for the newest span, regardless of the _end_at query parameter. - assert spans[0].name == tag_name - assert spans[0].value == 30.0 - assert spans[0].raw_start_at == time3 - assert spans[0].raw_end_at is None - - # Span 2 (from data point: time2) - assert spans[1].name == tag_name - assert spans[1].value == "val2" - assert spans[1].raw_start_at == time2 - assert spans[1].raw_end_at == time3 - - # Span 3 (from oldest data point: time1) - assert spans[2].name == tag_name - assert spans[2].value == 10.0 - assert spans[2].raw_start_at == time1 - assert spans[2].raw_end_at == time2 + @patch.object(CVec, "_login_with_supabase", return_value=None) + def test_get_metric_arrow_empty(self, mock_login: Any) -> None: + table = pa.table( + { + "name": pa.array([], type=pa.string()), + "time": pa.array([], type=pa.timestamp("us", tz=None)), + "value_double": pa.array([], type=pa.float64()), + "value_string": pa.array([], type=pa.string()), + } + ) + sink = pa.BufferOutputStream() + with ipc.new_file(sink, table.schema) as writer: + writer.write_table(table) + arrow_bytes = sink.getvalue().to_pybytes() + client = CVec( + host="test_host", + email="user@example.com", + password="password123", + publishable_key="test_publishable_key", + ) + client._make_request = lambda *args, **kwargs: arrow_bytes # type: ignore[method-assign] + result = client.get_metric_arrow(names=["non_existent_tag"]) + reader = ipc.open_file(io.BytesIO(result)) + result_table = reader.read_all() + assert result_table.num_rows == 0 + assert result_table.column("name").to_pylist() == [] + assert result_table.column("value_double").to_pylist() == [] + assert result_table.column("value_string").to_pylist() == []