├── tests ├── __init__.py └── test_client.py ├── chakra_py ├── __init__.py ├── exceptions.py └── client.py ├── banner.png ├── .github └── workflows │ ├── lint.yml │ └── test.yml ├── LICENSE ├── pyproject.toml ├── README.md ├── .gitignore └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chakra_py/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Chakra 2 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chakra-Network/python-sdk/HEAD/banner.png -------------------------------------------------------------------------------- /chakra_py/exceptions.py: -------------------------------------------------------------------------------- 1 | class ChakraAPIError(Exception): 2 | """Custom exception for Chakra API errors.""" 3 | 4 | def __init__(self, message: str, response=None): 5 | self.message = message 6 | self.response = response 7 | super().__init__(self.message) 8 | 9 | 10 | class ChakraAuthError(ChakraAPIError): 11 | """Custom exception for Chakra authentication errors.""" 12 | 13 | def __init__(self, message: str, response=None): 14 | super().__init__(message, response) 15 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: "3.12" 18 | - name: Install Poetry 19 | uses: snok/install-poetry@v1 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Check formatting with black 23 | run: poetry run black --check . 24 | - name: Check imports with isort 25 | run: poetry run isort --check-only . -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ["3.9", "3.10", "3.11", "3.12"] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install and configure Poetry 23 | uses: snok/install-poetry@v1 24 | with: 25 | version: 1.5.1 26 | - name: Install dependencies 27 | run: poetry install 28 | - name: Run tests with pytest 29 | run: poetry run pytest --cov=chakra_py --cov-report=xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Chakra Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "chakra-py" 3 | version = "1.0.24" 4 | description = "Interact with the Chakra API using Python + Pandas" 5 | authors = ["Chakra Labs Team"] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/Chakra-Network/python-sdk" 9 | keywords = ["chakra", "sdk", "api", "data", "pandas", "dataframe", "database"] 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Intended Audience :: Developers", 13 | "License :: OSI Approved :: MIT License", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | "Programming Language :: Python :: 3.11", 17 | "Programming Language :: Python :: 3.12", 18 | "Topic :: Software Development :: Libraries :: Python Modules", 19 | "Topic :: Database", 20 | ] 21 | 22 | [tool.poetry.dependencies] 23 | python = ">=3.9,<3.13" 24 | pandas = "^2.1.4" 25 | requests = "^2.32.3" 26 | python-dotenv = "^1.0.1" 27 | numpy = "^1.26.4" 28 | pyarrow = ">=14.0.1" 29 | colorama = "^0.4.6" 30 | tqdm = "^4.66.1" 31 | 32 | [tool.poetry.group.dev.dependencies] 33 | pytest = "^8.3.4" 34 | black = {version = "^24.10.0", python = ">=3.9"} 35 | isort = "^5.13.2" 36 | pytest-cov = "^4.1.0" 37 | 38 | [build-system] 39 | requires = ["poetry-core"] 40 | build-backend = "poetry.core.masonry.api" 41 | 42 | [tool.black] 43 | line-length = 88 44 | target-version = ['py38'] 45 | include = '\.pyi?$' 46 | 47 | [tool.isort] 48 | profile = "black" 49 | multi_line_output = 3 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chakra Python SDK 2 | 3 | [![PyPI version](https://badge.fury.io/py/chakra-py.svg)](https://badge.fury.io/py/chakra-py) 4 | [![Build Status](https://github.com/Chakra-Network/python-sdk/actions/workflows/test.yml/badge.svg)](https://github.com/Chakra-Network/python-sdk/actions/workflows/test.yml) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 6 | [![Python versions](https://img.shields.io/pypi/pyversions/chakra-py.svg)](https://pypi.org/project/chakra-py/) 7 | 8 | 9 | ![python_sdk](https://github.com/user-attachments/assets/d87bdee1-62b0-4ed8-b8ad-ec63c261a2ab) 10 | 11 | 12 | A Python SDK for interacting with the Chakra API. This SDK provides seamless integration with pandas DataFrames for data querying and manipulation. 13 | 14 | ## Features 15 | 16 | - **Token-based Authentication**: Secure authentication using DB Session keys 17 | - **Pandas Integration**: Query results automatically converted to pandas DataFrames 18 | - **Automatic Table Management**: Create and update tables with schema inference 19 | - **Batch Operations**: Efficient data pushing with batched inserts 20 | 21 | ## Installation 22 | 23 | ```bash 24 | pip install chakra-py 25 | ``` 26 | 27 | ## Finding your DB Session Key 28 | 29 | 1. Login to the [Chakra Console](https://console.chakra.dev/) 30 | 2. Select Settings 31 | 3. Navigate to the releveant database and copy the DB Session Key (not the access key or secret access key) 32 | 33 | https://github.com/user-attachments/assets/9f1c1ab8-cb87-42a1-8627-184617bbb7d7 34 | 35 | ## Quick Start 36 | 37 | ```python 38 | from chakra_py import Chakra 39 | import pandas as pd 40 | 41 | # Initialize client 42 | client = Chakra("YOUR_DB_SESSION_KEY") 43 | 44 | # Query data (returns pandas DataFrame) 45 | df = client.execute("SELECT * FROM my_table") 46 | print(df.head()) 47 | 48 | # Push data to a new or existing table 49 | data = pd.DataFrame({ 50 | "id": [1, 2, 3], 51 | "name": ["Alice", "Bob", "Charlie"], 52 | "score": [85.5, 92.0, 78.5] 53 | }) 54 | client.push("students", data, create_if_missing=True) 55 | ``` 56 | 57 | ## Querying Data 58 | 59 | Execute SQL queries and receive results as pandas DataFrames: 60 | 61 | ```python 62 | # Simple query 63 | df = client.execute("SELECT * FROM table_name") 64 | 65 | # Complex query with aggregations 66 | df = client.execute(""" 67 | SELECT 68 | category, 69 | COUNT(*) as count, 70 | AVG(value) as avg_value 71 | FROM measurements 72 | GROUP BY category 73 | HAVING count > 10 74 | ORDER BY avg_value DESC 75 | """) 76 | 77 | # Work with results using pandas 78 | print(df.describe()) 79 | print(df.groupby('category').agg({'value': ['mean', 'std']})) 80 | ``` 81 | 82 | ## Pushing Data 83 | 84 | Push data from pandas DataFrames to tables with automatic schema handling: 85 | 86 | ```python 87 | # Create a sample DataFrame 88 | df = pd.DataFrame({ 89 | 'id': range(1, 1001), 90 | 'name': [f'User_{i}' for i in range(1, 1001)], 91 | 'score': np.random.normal(75, 15, 1000).round(2), 92 | 'active': np.random.choice([True, False], 1000) 93 | }) 94 | 95 | # Create new table with inferred schema 96 | client.push( 97 | table_name="users", 98 | data=df, 99 | create_if_missing=True # Creates table if it doesn't exist 100 | ) 101 | 102 | # Update existing table 103 | new_users = pd.DataFrame({ 104 | 'id': range(1001, 1101), 105 | 'name': [f'User_{i}' for i in range(1001, 1101)], 106 | 'score': np.random.normal(75, 15, 100).round(2), 107 | 'active': np.random.choice([True, False], 100) 108 | }) 109 | client.push("users", new_users, create_if_missing=False) 110 | ``` 111 | 112 | The SDK automatically: 113 | - Infers appropriate column types from DataFrame dtypes 114 | - Creates tables with proper schema when needed 115 | - Handles NULL values and type conversions 116 | - Performs batch inserts for better performance 117 | 118 | ## Development 119 | 120 | To contribute to the SDK: 121 | 122 | 1. Clone the repository 123 | ```bash 124 | git clone https://github.com/Chakra-Network/python-sdk.git 125 | cd python-sdk 126 | ``` 127 | 128 | 2. Install development dependencies with Poetry 129 | ```bash 130 | # Install Poetry if you haven't already 131 | curl -sSL https://install.python-poetry.org | python3 - 132 | 133 | # Install dependencies 134 | poetry install 135 | ``` 136 | 137 | 3. Run tests 138 | ```bash 139 | poetry run pytest 140 | ``` 141 | 142 | 4. Build package 143 | ```bash 144 | poetry build 145 | ``` 146 | 147 | ## PyPI Publication 148 | 149 | The package is configured for easy PyPI publication: 150 | 151 | 1. Update version in `pyproject.toml` 152 | 2. Build distribution: `poetry build` 153 | 3. Publish: `poetry publish` 154 | 155 | ## License 156 | 157 | MIT License - see LICENSE file for details. 158 | 159 | ## Support 160 | 161 | For support and questions, please open an issue in the GitHub repository. 162 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python,macos 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### macOS Patch ### 34 | # iCloud generated files 35 | *.icloud 36 | 37 | ### Python ### 38 | # Byte-compiled / optimized / DLL files 39 | __pycache__/ 40 | *.py[cod] 41 | *$py.class 42 | 43 | # C extensions 44 | *.so 45 | 46 | # Distribution / packaging 47 | .Python 48 | build/ 49 | develop-eggs/ 50 | dist/ 51 | downloads/ 52 | eggs/ 53 | .eggs/ 54 | lib/ 55 | lib64/ 56 | parts/ 57 | sdist/ 58 | var/ 59 | wheels/ 60 | share/python-wheels/ 61 | *.egg-info/ 62 | .installed.cfg 63 | *.egg 64 | MANIFEST 65 | 66 | # PyInstaller 67 | # Usually these files are written by a python script from a template 68 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 69 | *.manifest 70 | *.spec 71 | 72 | # Installer logs 73 | pip-log.txt 74 | pip-delete-this-directory.txt 75 | 76 | # Unit test / coverage reports 77 | htmlcov/ 78 | .tox/ 79 | .nox/ 80 | .coverage 81 | .coverage.* 82 | .cache 83 | nosetests.xml 84 | coverage.xml 85 | *.cover 86 | *.py,cover 87 | .hypothesis/ 88 | .pytest_cache/ 89 | cover/ 90 | 91 | # Translations 92 | *.mo 93 | *.pot 94 | 95 | # Django stuff: 96 | *.log 97 | local_settings.py 98 | db.sqlite3 99 | db.sqlite3-journal 100 | 101 | # Flask stuff: 102 | instance/ 103 | .webassets-cache 104 | 105 | # Scrapy stuff: 106 | .scrapy 107 | 108 | # Sphinx documentation 109 | docs/_build/ 110 | 111 | # PyBuilder 112 | .pybuilder/ 113 | target/ 114 | 115 | # Jupyter Notebook 116 | .ipynb_checkpoints 117 | 118 | # IPython 119 | profile_default/ 120 | ipython_config.py 121 | 122 | # pyenv 123 | # For a library or package, you might want to ignore these files since the code is 124 | # intended to run in multiple environments; otherwise, check them in: 125 | # .python-version 126 | 127 | # pipenv 128 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 129 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 130 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 131 | # install all needed dependencies. 132 | #Pipfile.lock 133 | 134 | # poetry 135 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 136 | # This is especially recommended for binary packages to ensure reproducibility, and is more 137 | # commonly ignored for libraries. 138 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 139 | #poetry.lock 140 | 141 | # pdm 142 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 143 | #pdm.lock 144 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 145 | # in version control. 146 | # https://pdm.fming.dev/#use-with-ide 147 | .pdm.toml 148 | 149 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 150 | __pypackages__/ 151 | 152 | # Celery stuff 153 | celerybeat-schedule 154 | celerybeat.pid 155 | 156 | # SageMath parsed files 157 | *.sage.py 158 | 159 | # Environments 160 | .env 161 | .venv 162 | env/ 163 | venv/ 164 | ENV/ 165 | env.bak/ 166 | venv.bak/ 167 | 168 | # Spyder project settings 169 | .spyderproject 170 | .spyproject 171 | 172 | # Rope project settings 173 | .ropeproject 174 | 175 | # mkdocs documentation 176 | /site 177 | 178 | # mypy 179 | .mypy_cache/ 180 | .dmypy.json 181 | dmypy.json 182 | 183 | # Pyre type checker 184 | .pyre/ 185 | 186 | # pytype static type analyzer 187 | .pytype/ 188 | 189 | # Cython debug symbols 190 | cython_debug/ 191 | 192 | # PyCharm 193 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 194 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 195 | # and can be added to the global gitignore or merged into this file. For a more nuclear 196 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 197 | #.idea/ 198 | 199 | ### Python Patch ### 200 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 201 | poetry.toml 202 | 203 | # ruff 204 | .ruff_cache/ 205 | 206 | # LSP config files 207 | pyrightconfig.json 208 | 209 | # End of https://www.toptal.com/developers/gitignore/api/python,macos -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, patch 2 | 3 | import pandas as pd 4 | import pytest 5 | import requests 6 | 7 | from chakra_py import Chakra 8 | 9 | 10 | def test_client_initialization(): 11 | """Test basic client initialization.""" 12 | client = Chakra("access:secret:username") 13 | assert client.token is None 14 | assert isinstance(client._session, requests.Session) 15 | 16 | 17 | @patch("requests.Session") 18 | def test_auth_login(mock_session): 19 | """Test authentication login.""" 20 | # Mock the token fetch response 21 | mock_response = Mock() 22 | mock_response.json.return_value = {"token": "DDB_test123"} 23 | mock_session.return_value.post.return_value = mock_response 24 | 25 | # Create a mock headers dictionary 26 | mock_session.return_value.headers = {} 27 | 28 | client = Chakra("access:secret:username") 29 | client.login() 30 | 31 | # Verify the token fetch request 32 | mock_session.return_value.post.assert_called_with( 33 | "https://api.chakra.dev/api/v1/servers", 34 | json={"accessKey": "access", "secretKey": "secret", "username": "username"}, 35 | ) 36 | 37 | assert client.token == "DDB_test123" 38 | # Verify the actual headers dictionary instead of the __getitem__ call 39 | assert mock_session.return_value.headers["Authorization"] == "Bearer DDB_test123" 40 | 41 | 42 | @patch("requests.Session") 43 | def test_query_execution(mock_session): 44 | """Test query execution and DataFrame conversion.""" 45 | # Mock the token fetch response 46 | mock_auth_response = Mock() 47 | mock_auth_response.json.return_value = {"token": "DDB_test123"} 48 | 49 | # Mock the query response 50 | mock_query_response = Mock() 51 | mock_query_response.json.return_value = { 52 | "columns": ["id", "name"], 53 | "rows": [[1, "test"], [2, "test2"]], 54 | } 55 | 56 | mock_session.return_value.post.side_effect = [ 57 | mock_auth_response, 58 | mock_query_response, 59 | ] 60 | 61 | # Initialize headers dictionary 62 | mock_session.return_value.headers = {} 63 | 64 | client = Chakra("access:secret:username") 65 | client.login() 66 | df = client.execute("SELECT * FROM test_table") 67 | 68 | # Verify DataFrame 69 | assert isinstance(df, pd.DataFrame) 70 | assert list(df.columns) == ["id", "name"] 71 | assert len(df) == 2 72 | 73 | 74 | @patch("uuid.uuid4") 75 | @patch("requests.put") 76 | @patch("requests.Session") 77 | def test_data_push(mock_session, mock_requests_put, mock_uuid4): 78 | """Test data push functionality.""" 79 | mock_uuid = "fake-uuid-1234" 80 | mock_uuid4.return_value = mock_uuid 81 | 82 | # Initialize headers dictionary 83 | mock_session.return_value.headers = {} 84 | 85 | # Mock responses 86 | mock_auth_response = Mock() 87 | mock_auth_response.json.return_value = {"token": "DDB_test123"} 88 | 89 | mock_presigned_response = Mock() 90 | mock_presigned_response.json.return_value = { 91 | "presignedUrl": "https://fake-s3-url.com", 92 | "key": "fake-s3-key", 93 | } 94 | mock_session.return_value.get.return_value = mock_presigned_response 95 | 96 | # Set up all mock responses in the correct order 97 | mock_session.return_value.post.side_effect = [ 98 | mock_auth_response, # For login 99 | Mock(status_code=200), # For create database 100 | Mock(status_code=200), # For create schema 101 | Mock(status_code=200), # For create table 102 | Mock(status_code=200), # For batch insert 103 | mock_presigned_response, # For presigned URL 104 | Mock(status_code=200), # For import 105 | Mock(status_code=200), # For delete 106 | ] 107 | 108 | mock_requests_put.return_value = Mock(status_code=200) 109 | 110 | # Create test DataFrame 111 | df = pd.DataFrame({"id": [1, 2], "name": ["test1", "test2"]}) 112 | 113 | # Create client and login first 114 | client = Chakra("access:secret:username") 115 | client.login() # This will consume the first mock response 116 | 117 | # Now test pushing data 118 | client.push("test_database.test_schema.test_table", df) 119 | 120 | # 1. Verify the presigned URL GET request 121 | presigned_get_call = mock_session.return_value.get.call_args 122 | assert presigned_get_call[0][ 123 | 0 124 | ] == "https://api.chakra.dev/api/v1/presigned-upload?filename=test_database.test_schema.test_table_{}.parquet".format( 125 | mock_uuid 126 | ) 127 | 128 | # 2. Verify the S3 upload was called with correct parameters 129 | mock_requests_put.assert_called_once() 130 | put_args = mock_requests_put.call_args 131 | assert put_args[0][0] == "https://fake-s3-url.com" 132 | assert put_args[1]["headers"] == {"Content-Type": "application/parquet"} 133 | assert "data" in put_args[1] 134 | 135 | # 3. Verify the create database request 136 | create_db_call = mock_session.return_value.post.call_args_list[1] 137 | assert create_db_call[0][0] == "https://api.chakra.dev/api/v1/databases" 138 | assert create_db_call[1]["json"] == { 139 | "name": "test_database", 140 | "insert_database": True, 141 | } 142 | 143 | # 4. Verify the create schema request 144 | create_schema_call = mock_session.return_value.post.call_args_list[2] 145 | assert create_schema_call[0][0] == "https://api.chakra.dev/api/v1/query" 146 | assert create_schema_call[1]["json"] == { 147 | "sql": "CREATE SCHEMA IF NOT EXISTS test_database.test_schema" 148 | } 149 | 150 | # 5. Verify the create table request 151 | create_table_call = mock_session.return_value.post.call_args_list[3] 152 | assert create_table_call[0][0] == "https://api.chakra.dev/api/v1/query" 153 | assert create_table_call[1]["json"] == { 154 | "sql": "CREATE TABLE IF NOT EXISTS test_database.test_schema.test_table (id BIGINT, name VARCHAR)" 155 | } 156 | 157 | # 6. Verify the import request 158 | import_call = mock_session.return_value.post.call_args_list[4] 159 | assert import_call[0][0] == "https://api.chakra.dev/api/v1/tables/s3_parquet_import" 160 | assert import_call[1]["json"] == { 161 | "table_name": "test_database.test_schema.test_table", 162 | "s3_key": "fake-s3-key", 163 | } 164 | 165 | # 7. Verify cleanup was called 166 | delete_call = mock_session.return_value.delete.call_args 167 | assert delete_call[0][0] == "https://api.chakra.dev/api/v1/files" 168 | assert delete_call[1]["json"] == {"fileName": "fake-s3-key"} 169 | -------------------------------------------------------------------------------- /chakra_py/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import uuid 4 | from typing import Any, Dict, Optional, Union 5 | 6 | import pandas as pd 7 | import requests 8 | from colorama import Fore, Style 9 | from tqdm import tqdm 10 | 11 | from .exceptions import ChakraAPIError, ChakraAuthError 12 | 13 | BASE_URL = "https://api.chakra.dev".rstrip("/") 14 | 15 | DEFAULT_BATCH_SIZE = 1000 16 | TOKEN_PREFIX = "DDB_" 17 | 18 | 19 | __version__ = "1.0.24" 20 | __all__ = ["Chakra"] 21 | 22 | BANNER = rf"""{Fore.GREEN} 23 | _____ _ _ ________ __ 24 | / __ \ | | | | ___ \ \ / / 25 | | / \/ |__ __ _| | ___ __ __ _ | |_/ /\ V / 26 | | | | '_ \ / _` | |/ / '__/ _` | | __/ \ / 27 | | \__/\ | | | (_| | <| | | (_| | | | | | 28 | \____/_| |_|\__,_|_|\_\_| \__,_| \_| \_/ 29 | {Style.RESET_ALL} 30 | 31 | Python SDK v{__version__} 32 | """ 33 | 34 | 35 | class ProgressFileWrapper: 36 | def __init__(self, file, total_size, tdqm): 37 | self.file = file 38 | self.progress_bar = tdqm 39 | self._len = total_size 40 | 41 | def read(self, size=-1): 42 | data = self.file.read(size) 43 | if data: 44 | self.progress_bar.update(len(data)) 45 | return data 46 | 47 | def __len__(self): 48 | return self._len 49 | 50 | 51 | def ensure_authenticated(func): 52 | """Decorator to ensure the client is authenticated before executing a method.""" 53 | 54 | def wrapper(self, *args, **kwargs): 55 | max_attempts = 3 56 | attempt = 0 57 | 58 | while attempt < max_attempts: 59 | if not self.token: 60 | self.login() 61 | try: 62 | return func(self, *args, **kwargs) 63 | except Exception as e: 64 | if ( 65 | isinstance(e, requests.exceptions.HTTPError) 66 | and e.response.status_code == 401 67 | ) or (isinstance(e, ChakraAPIError) and e.response.status_code == 401): 68 | attempt += 1 69 | print( 70 | f"Attempt {attempt} failed with 401. Stale token. Attempting login..." 71 | ) 72 | self.login() 73 | else: 74 | attempt += 1 75 | print( 76 | f"Attempt {attempt} failed with 401. Stale token. Attempting login..." 77 | ) 78 | raise 79 | raise ChakraAuthError("Failed to authenticate after 3 attempts.") 80 | 81 | return wrapper 82 | 83 | 84 | class Chakra: 85 | """Main client for interacting with the Chakra API. 86 | 87 | Provides a simple, unified interface for all Chakra operations including 88 | authentication, querying, and data manipulation. 89 | 90 | Example: 91 | >>> client = Chakra("DB_SESSION_KEY") 92 | >>> df = client.execute("SELECT * FROM table") 93 | >>> client.push("new_table", df) 94 | """ 95 | 96 | def __init__( 97 | self, 98 | db_session_key: str, 99 | quiet: bool = False, 100 | ): 101 | """Initialize the Chakra client. 102 | 103 | Args: 104 | db_session_key: The DB session key to use - can be found in the Chakra Settings page 105 | quiet: If True, suppresses all stdout messages (default: False) 106 | """ 107 | self._db_session_key = db_session_key 108 | self._token = None 109 | self._session = requests.Session() 110 | self._quiet = quiet 111 | 112 | if not quiet: 113 | print(BANNER.format(version=__version__)) 114 | 115 | @property 116 | def token(self) -> Optional[str]: 117 | return self._token 118 | 119 | @token.setter 120 | def token(self, value: str): 121 | self._token = value 122 | if value: 123 | self._session.headers.update({"Authorization": f"Bearer {value}"}) 124 | else: 125 | self._session.headers.pop("Authorization", None) 126 | 127 | def _fetch_token(self, db_session_key: str) -> str: 128 | """Fetch a token from the Chakra API. 129 | 130 | Args: 131 | db_session_key: The DB session key to use 132 | 133 | Returns: 134 | The token to use for authentication 135 | """ 136 | access_key_id, secret_access_key, username = db_session_key.split(":") 137 | 138 | response = self._session.post( 139 | f"{BASE_URL}/api/v1/servers", 140 | json={ 141 | "accessKey": access_key_id, 142 | "secretKey": secret_access_key, 143 | "username": username, 144 | }, 145 | ) 146 | response.raise_for_status() 147 | return response.json()["token"] 148 | 149 | def _create_database_and_schema(self, table_name: str, pbar: tqdm) -> None: 150 | """Create database, schema, and table if they don't exist.""" 151 | pbar.set_description("Creating database if it doesn't exist...") 152 | [database_name, schema_name, _] = table_name.split(".") 153 | response = self._session.post( 154 | f"{BASE_URL}/api/v1/databases", 155 | json={"name": database_name, "insert_database": True}, 156 | ) 157 | if response.status_code != 409: 158 | # only raise error if the database doesn't already exist 159 | response.raise_for_status() 160 | 161 | pbar.set_description(f"Creating schema {schema_name} if it doesn't exist...") 162 | 163 | create_sql = f"CREATE SCHEMA IF NOT EXISTS {database_name}.{schema_name}" 164 | response = self._session.post( 165 | f"{BASE_URL}/api/v1/query", json={"sql": create_sql} 166 | ) 167 | response.raise_for_status() 168 | 169 | def _create_table_schema( 170 | self, table_name: str, data: pd.DataFrame, pbar: tqdm 171 | ) -> None: 172 | """Create table schema if it doesn't exist.""" 173 | pbar.set_description("Creating table schema...") 174 | columns = [ 175 | {"name": col, "type": self._map_pandas_to_duckdb_type(dtype)} 176 | for col, dtype in data.dtypes.items() 177 | ] 178 | create_sql = f"CREATE TABLE IF NOT EXISTS {table_name} (" 179 | create_sql += ", ".join(f"{col['name']} {col['type']}" for col in columns) 180 | create_sql += ")" 181 | 182 | response = self._session.post( 183 | f"{BASE_URL}/api/v1/query", json={"sql": create_sql} 184 | ) 185 | response.raise_for_status() 186 | 187 | def _replace_existing_table(self, table_name: str, pbar: tqdm) -> None: 188 | """Drop existing table if replace_if_exists is True.""" 189 | pbar.set_description(f"Replacing table...") 190 | response = self._session.post( 191 | f"{BASE_URL}/api/v1/query", 192 | json={"sql": f"DROP TABLE IF EXISTS {table_name}"}, 193 | ) 194 | response.raise_for_status() 195 | 196 | def _process_batch( 197 | self, table_name: str, batch: list, batch_number: int, pbar: tqdm 198 | ) -> None: 199 | """Process and upload a single batch of records.""" 200 | # Create placeholders for the batch 201 | value_placeholders = "(" + ", ".join(["?" for _ in batch[0]]) + ")" 202 | batch_placeholders = ", ".join([value_placeholders for _ in batch]) 203 | insert_sql = f"INSERT INTO {table_name} VALUES {batch_placeholders}" 204 | 205 | # Flatten parameters for this batch 206 | parameters = [ 207 | str(value) if pd.notna(value) else "NULL" 208 | for record in batch 209 | for value in record.values() 210 | ] 211 | 212 | pbar.set_description(f"Uploading batch {batch_number}...") 213 | response = self._session.post( 214 | f"{BASE_URL}/api/v1/query", 215 | json={"sql": insert_sql, "parameters": parameters}, 216 | ) 217 | response.raise_for_status() 218 | 219 | def _request_presigned_url(self, file_name: str) -> dict: 220 | """Request a presigned URL for the upload.""" 221 | response = self._session.get( 222 | f"{BASE_URL}/api/v1/presigned-upload?filename={file_name}", 223 | ) 224 | response.raise_for_status() 225 | return response.json() 226 | 227 | def _upload_parquet_using_presigned_url( 228 | self, presigned_url: str, file: str, file_size: int, pbar: tqdm 229 | ) -> None: 230 | """Upload a parquet file to S3 using a presigned URL.""" 231 | progress_wrapper = ProgressFileWrapper(file, file_size, pbar) 232 | 233 | pbar.set_description("Uploading data...") 234 | response = requests.put( 235 | presigned_url, 236 | data=progress_wrapper, 237 | headers={"Content-Type": "application/parquet"}, 238 | ) 239 | response.raise_for_status() 240 | 241 | def _import_data_from_presigned_url(self, table_name: str, s3_key: str) -> None: 242 | """Import data from a presigned URL into a table.""" 243 | response = self._session.post( 244 | f"{BASE_URL}/api/v1/tables/s3_parquet_import", 245 | json={"table_name": table_name, "s3_key": s3_key}, 246 | ) 247 | response.raise_for_status() 248 | 249 | def _import_data_from_append_only_dedupe_presigned_url( 250 | self, table_name: str, s3_key: str, primary_key_columns: list[str] 251 | ) -> None: 252 | """Import data from a presigned URL into a table.""" 253 | response = self._session.post( 254 | f"{BASE_URL}/api/v1/tables/s3_parquet_import_append_only_dedupe", 255 | json={ 256 | "table_name": table_name, 257 | "s3_key": s3_key, 258 | "primary_key_columns": primary_key_columns, 259 | }, 260 | ) 261 | response.raise_for_status() 262 | 263 | def _delete_file_from_s3(self, s3_key: str) -> None: 264 | """Delete a file from S3.""" 265 | response = self._session.delete( 266 | f"{BASE_URL}/api/v1/files", 267 | json={"fileName": s3_key}, 268 | ) 269 | response.raise_for_status() 270 | 271 | def _print(self, message: str) -> None: 272 | """Print a message if quiet mode is not enabled.""" 273 | if not self._quiet: 274 | print(message) 275 | 276 | @ensure_authenticated 277 | def push( 278 | self, 279 | table_name: str, 280 | data: pd.DataFrame, 281 | create_if_missing: bool = True, 282 | replace_if_exists: bool = False, 283 | dedupe_on_append: bool = False, 284 | primary_key_columns: list[str] = [], 285 | ) -> None: 286 | # Validate table name format 287 | if table_name.count(".") != 0 and table_name.count(".") != 2: 288 | raise ValueError( 289 | "Table name must be either a simple table name (e.g., 'my_table') or fully qualified with database and schema (e.g., 'my_database.my_schema.my_table')" 290 | ) 291 | 292 | if table_name.count(".") == 0: 293 | table_name = f"duckdb.main.{table_name}" 294 | 295 | """Push data to a table.""" 296 | if not self.token: 297 | raise ValueError("Authentication required") 298 | 299 | total_records = len(data) 300 | 301 | with tempfile.NamedTemporaryFile() as temp_file: 302 | data.to_parquet( 303 | temp_file.name, 304 | engine="pyarrow", 305 | compression="zstd", 306 | index=False, 307 | ) 308 | file_size = os.path.getsize(temp_file.name) 309 | 310 | with tqdm( 311 | total=file_size + 2, 312 | desc="Uploading data...", 313 | bar_format="{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]", 314 | colour="green", 315 | unit="B", 316 | unit_scale=True, 317 | disable=self._quiet, 318 | ) as pbar: 319 | try: 320 | if create_if_missing or replace_if_exists: 321 | self._create_database_and_schema(table_name, pbar) 322 | 323 | if replace_if_exists: 324 | self._replace_existing_table(table_name, pbar) 325 | 326 | if create_if_missing or replace_if_exists: 327 | self._create_table_schema(table_name, data, pbar) 328 | 329 | # Request a presigned URL for the upload 330 | uuid_str = str(uuid.uuid4()) 331 | filename = f"{table_name}_{uuid_str}.parquet" 332 | response = self._request_presigned_url(filename) 333 | presigned_url = response["presignedUrl"] 334 | s3_key = response["key"] 335 | 336 | # Upload the data to the presigned URL 337 | temp_file.seek(0) 338 | self._upload_parquet_using_presigned_url( 339 | presigned_url, temp_file, file_size, pbar 340 | ) 341 | 342 | # Import the data into the warehouse from the presigned URL 343 | pbar.set_description("Importing data into warehouse...") 344 | if dedupe_on_append: 345 | self._import_data_from_append_only_dedupe_presigned_url( 346 | table_name, s3_key, primary_key_columns 347 | ) 348 | else: 349 | self._import_data_from_presigned_url(table_name, s3_key) 350 | pbar.update(1) 351 | 352 | # Clean up the data that was previously uploaded 353 | pbar.set_description("Cleaning up...") 354 | self._delete_file_from_s3(s3_key) 355 | pbar.update(1) 356 | 357 | pbar.set_description("Data import finished.") 358 | 359 | except Exception as e: 360 | self._handle_api_error(e) 361 | 362 | self._print( 363 | f"{Fore.GREEN}✓ Successfully pushed {total_records} records to {table_name}!{Style.RESET_ALL}\n" 364 | ) 365 | 366 | def login(self) -> None: 367 | """Set the authentication token for API requests.""" 368 | self._print(f"\n{Fore.GREEN}Authenticating with Chakra DB...{Style.RESET_ALL}") 369 | 370 | with tqdm( 371 | total=100, 372 | desc="Authenticating", 373 | bar_format="{l_bar}{bar}| {n:.0f}%", 374 | colour="green", 375 | disable=self._quiet, 376 | ) as pbar: 377 | pbar.update(30) 378 | pbar.set_description("Fetching token...") 379 | self.token = self._fetch_token(self._db_session_key) 380 | 381 | pbar.update(40) 382 | pbar.set_description("Token fetched") 383 | if not self.token.startswith(TOKEN_PREFIX): 384 | raise ValueError(f"Token must start with '{TOKEN_PREFIX}'") 385 | 386 | pbar.update(30) 387 | pbar.set_description("Authentication complete") 388 | 389 | self._print(f"{Fore.GREEN}✓ Successfully authenticated!{Style.RESET_ALL}\n") 390 | 391 | # HACK: this is a hack to get around the fact that the duckdb go doesn't support positional parameters 392 | def __query_has_positional_parameters(self, query: str) -> bool: 393 | """Check if the query has positional parameters.""" 394 | return "$1" in query 395 | 396 | def __replace_position_parameters_with_autoincrement( 397 | self, query: str, parameters: list 398 | ) -> str: 399 | """Replace positional parameters with autoincrement.""" 400 | if len(parameters) > 9: 401 | raise ValueError( 402 | "Chakra DB does not support more than 8 positional parameters" 403 | ) 404 | # find all $1, $2, $3, etc. and replace with ?, ?, ?, etc. 405 | new_query = query 406 | for i in range(len(parameters)): 407 | new_query = new_query.replace(f"${i+1}", f"?") 408 | 409 | # explode the parameters into a single list with duplicates aligned 410 | new_parameters = [] 411 | # iterate over query, find the relevant index in parameters, then add the value to new_parameters 412 | for i in range(len(query)): 413 | if query[i] == "$": 414 | index = int(query[i + 1]) 415 | # duckdb uses 1-indexed parameters, so we need to subtract 1 416 | new_parameters.append(parameters[index - 1]) 417 | 418 | return new_query, new_parameters 419 | 420 | @ensure_authenticated 421 | def execute(self, query: str, parameters: list = []) -> pd.DataFrame: 422 | """Execute a query and return results as a pandas DataFrame.""" 423 | if not self.token: 424 | raise ValueError("Authentication required") 425 | 426 | with tqdm( 427 | total=3, 428 | desc="Preparing query...", 429 | bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} steps", 430 | colour="green", 431 | disable=self._quiet, 432 | ) as pbar: 433 | try: 434 | if self.__query_has_positional_parameters(query): 435 | query, parameters = ( 436 | self.__replace_position_parameters_with_autoincrement( 437 | query, parameters 438 | ) 439 | ) 440 | pbar.set_description("Executing query...") 441 | response = self._session.post( 442 | f"{BASE_URL}/api/v1/query", 443 | json={"sql": query, "parameters": parameters}, 444 | ) 445 | response.raise_for_status() 446 | pbar.update(1) 447 | 448 | pbar.set_description("Processing results...") 449 | data = response.json() 450 | pbar.update(1) 451 | 452 | pbar.set_description("Building DataFrame...") 453 | df = pd.DataFrame(data["rows"], columns=data["columns"]) 454 | pbar.update(1) 455 | 456 | pbar.set_description("Query execution finished.") 457 | except Exception as e: 458 | self._handle_api_error(e) 459 | 460 | self._print(f"{Fore.GREEN}✓ Query executed successfully!{Style.RESET_ALL}\n") 461 | return df 462 | 463 | def _map_pandas_to_duckdb_type(self, dtype) -> str: 464 | """Convert pandas dtype to DuckDB type. 465 | 466 | Args: 467 | dtype: Pandas dtype object 468 | 469 | Returns: 470 | str: Corresponding DuckDB type name 471 | """ 472 | dtype_str = str(dtype) 473 | if "int" in dtype_str: 474 | return "BIGINT" 475 | elif "float" in dtype_str: 476 | return "DOUBLE" 477 | elif "bool" in dtype_str: 478 | return "BOOLEAN" 479 | elif "datetime" in dtype_str: 480 | return "TIMESTAMP" 481 | elif "timedelta" in dtype_str: 482 | return "INTERVAL" 483 | elif "object" in dtype_str: 484 | return "VARCHAR" 485 | else: 486 | return "VARCHAR" # Default fallback 487 | 488 | def _handle_api_error(self, e: Exception) -> None: 489 | """Handle API errors consistently. 490 | 491 | Args: 492 | e: The original exception 493 | 494 | Raises: 495 | ChakraAPIError: Enhanced error with API response details 496 | """ 497 | if hasattr(e, "response") and hasattr(e.response, "json"): 498 | try: 499 | error_msg = e.response.json().get("error", str(e)) 500 | raise ChakraAPIError(error_msg, e.response) from e 501 | except ValueError: # JSON decoding failed 502 | raise ChakraAPIError(str(e), e.response) from e 503 | raise e # Re-raise original exception if not an API error 504 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "black" 5 | version = "24.10.0" 6 | description = "The uncompromising code formatter." 7 | optional = false 8 | python-versions = ">=3.9" 9 | files = [ 10 | {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, 11 | {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, 12 | {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, 13 | {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, 14 | {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, 15 | {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, 16 | {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, 17 | {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, 18 | {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, 19 | {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, 20 | {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, 21 | {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, 22 | {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, 23 | {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, 24 | {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, 25 | {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, 26 | {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, 27 | {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, 28 | {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, 29 | {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, 30 | {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, 31 | {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, 32 | ] 33 | 34 | [package.dependencies] 35 | click = ">=8.0.0" 36 | mypy-extensions = ">=0.4.3" 37 | packaging = ">=22.0" 38 | pathspec = ">=0.9.0" 39 | platformdirs = ">=2" 40 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 41 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 42 | 43 | [package.extras] 44 | colorama = ["colorama (>=0.4.3)"] 45 | d = ["aiohttp (>=3.10)"] 46 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 47 | uvloop = ["uvloop (>=0.15.2)"] 48 | 49 | [[package]] 50 | name = "certifi" 51 | version = "2024.12.14" 52 | description = "Python package for providing Mozilla's CA Bundle." 53 | optional = false 54 | python-versions = ">=3.6" 55 | files = [ 56 | {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, 57 | {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, 58 | ] 59 | 60 | [[package]] 61 | name = "charset-normalizer" 62 | version = "3.4.1" 63 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 64 | optional = false 65 | python-versions = ">=3.7" 66 | files = [ 67 | {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, 68 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, 69 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, 70 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, 71 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, 72 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, 73 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, 74 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, 75 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, 76 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, 77 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, 78 | {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, 79 | {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, 80 | {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, 81 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, 82 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, 83 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, 84 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, 85 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, 86 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, 87 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, 88 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, 89 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, 90 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, 91 | {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, 92 | {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, 93 | {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, 94 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, 95 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, 96 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, 97 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, 98 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, 99 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, 100 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, 101 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, 102 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, 103 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, 104 | {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, 105 | {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, 106 | {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, 107 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, 108 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, 109 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, 110 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, 111 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, 112 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, 113 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, 114 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, 115 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, 116 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, 117 | {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, 118 | {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, 119 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, 120 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, 121 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, 122 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, 123 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, 124 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, 125 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, 126 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, 127 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, 128 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, 129 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, 130 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, 131 | {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, 132 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, 133 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, 134 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, 135 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, 136 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, 137 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, 138 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, 139 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, 140 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, 141 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, 142 | {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, 143 | {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, 144 | {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, 145 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, 146 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, 147 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, 148 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, 149 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, 150 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, 151 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, 152 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, 153 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, 154 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, 155 | {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, 156 | {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, 157 | {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, 158 | {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, 159 | ] 160 | 161 | [[package]] 162 | name = "click" 163 | version = "8.1.8" 164 | description = "Composable command line interface toolkit" 165 | optional = false 166 | python-versions = ">=3.7" 167 | files = [ 168 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 169 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 170 | ] 171 | 172 | [package.dependencies] 173 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 174 | 175 | [[package]] 176 | name = "colorama" 177 | version = "0.4.6" 178 | description = "Cross-platform colored terminal text." 179 | optional = false 180 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 181 | files = [ 182 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 183 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 184 | ] 185 | 186 | [[package]] 187 | name = "coverage" 188 | version = "7.6.10" 189 | description = "Code coverage measurement for Python" 190 | optional = false 191 | python-versions = ">=3.9" 192 | files = [ 193 | {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, 194 | {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, 195 | {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, 196 | {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, 197 | {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, 198 | {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, 199 | {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, 200 | {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, 201 | {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, 202 | {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, 203 | {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, 204 | {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, 205 | {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, 206 | {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, 207 | {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, 208 | {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, 209 | {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, 210 | {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, 211 | {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, 212 | {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, 213 | {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, 214 | {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, 215 | {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, 216 | {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, 217 | {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, 218 | {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, 219 | {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, 220 | {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, 221 | {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, 222 | {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, 223 | {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, 224 | {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, 225 | {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, 226 | {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, 227 | {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, 228 | {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, 229 | {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, 230 | {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, 231 | {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, 232 | {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, 233 | {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, 234 | {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, 235 | {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, 236 | {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, 237 | {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, 238 | {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, 239 | {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, 240 | {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, 241 | {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, 242 | {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, 243 | {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, 244 | {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, 245 | {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, 246 | {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, 247 | {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, 248 | {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, 249 | {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, 250 | {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, 251 | {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, 252 | {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, 253 | {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, 254 | {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, 255 | ] 256 | 257 | [package.dependencies] 258 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 259 | 260 | [package.extras] 261 | toml = ["tomli"] 262 | 263 | [[package]] 264 | name = "exceptiongroup" 265 | version = "1.2.2" 266 | description = "Backport of PEP 654 (exception groups)" 267 | optional = false 268 | python-versions = ">=3.7" 269 | files = [ 270 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 271 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 272 | ] 273 | 274 | [package.extras] 275 | test = ["pytest (>=6)"] 276 | 277 | [[package]] 278 | name = "idna" 279 | version = "3.10" 280 | description = "Internationalized Domain Names in Applications (IDNA)" 281 | optional = false 282 | python-versions = ">=3.6" 283 | files = [ 284 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 285 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 286 | ] 287 | 288 | [package.extras] 289 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 290 | 291 | [[package]] 292 | name = "iniconfig" 293 | version = "2.0.0" 294 | description = "brain-dead simple config-ini parsing" 295 | optional = false 296 | python-versions = ">=3.7" 297 | files = [ 298 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 299 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 300 | ] 301 | 302 | [[package]] 303 | name = "isort" 304 | version = "5.13.2" 305 | description = "A Python utility / library to sort Python imports." 306 | optional = false 307 | python-versions = ">=3.8.0" 308 | files = [ 309 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 310 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 311 | ] 312 | 313 | [package.extras] 314 | colors = ["colorama (>=0.4.6)"] 315 | 316 | [[package]] 317 | name = "mypy-extensions" 318 | version = "1.0.0" 319 | description = "Type system extensions for programs checked with the mypy type checker." 320 | optional = false 321 | python-versions = ">=3.5" 322 | files = [ 323 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 324 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 325 | ] 326 | 327 | [[package]] 328 | name = "numpy" 329 | version = "2.0.2" 330 | description = "Fundamental package for array computing in Python" 331 | optional = false 332 | python-versions = ">=3.9" 333 | files = [ 334 | {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, 335 | {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, 336 | {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, 337 | {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, 338 | {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, 339 | {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, 340 | {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, 341 | {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, 342 | {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, 343 | {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, 344 | {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, 345 | {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, 346 | {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, 347 | {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, 348 | {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, 349 | {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, 350 | {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, 351 | {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, 352 | {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, 353 | {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, 354 | {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, 355 | {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, 356 | {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, 357 | {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, 358 | {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, 359 | {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, 360 | {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, 361 | {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, 362 | {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, 363 | {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, 364 | {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, 365 | {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, 366 | {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, 367 | {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, 368 | {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, 369 | {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, 370 | {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, 371 | {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, 372 | {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, 373 | {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, 374 | {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, 375 | {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, 376 | {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, 377 | {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, 378 | {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, 379 | ] 380 | 381 | [[package]] 382 | name = "packaging" 383 | version = "24.2" 384 | description = "Core utilities for Python packages" 385 | optional = false 386 | python-versions = ">=3.8" 387 | files = [ 388 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 389 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 390 | ] 391 | 392 | [[package]] 393 | name = "pandas" 394 | version = "2.2.3" 395 | description = "Powerful data structures for data analysis, time series, and statistics" 396 | optional = false 397 | python-versions = ">=3.9" 398 | files = [ 399 | {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, 400 | {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, 401 | {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, 402 | {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, 403 | {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, 404 | {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, 405 | {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, 406 | {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, 407 | {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, 408 | {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, 409 | {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, 410 | {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, 411 | {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, 412 | {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, 413 | {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, 414 | {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, 415 | {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, 416 | {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, 417 | {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, 418 | {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, 419 | {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, 420 | {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, 421 | {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, 422 | {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, 423 | {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, 424 | {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, 425 | {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, 426 | {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, 427 | {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, 428 | {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, 429 | {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, 430 | {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, 431 | {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, 432 | {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, 433 | {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, 434 | {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, 435 | {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, 436 | {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, 437 | {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, 438 | {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, 439 | {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, 440 | {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, 441 | ] 442 | 443 | [package.dependencies] 444 | numpy = [ 445 | {version = ">=1.22.4", markers = "python_version < \"3.11\""}, 446 | {version = ">=1.23.2", markers = "python_version == \"3.11\""}, 447 | {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, 448 | ] 449 | python-dateutil = ">=2.8.2" 450 | pytz = ">=2020.1" 451 | tzdata = ">=2022.7" 452 | 453 | [package.extras] 454 | 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)"] 455 | aws = ["s3fs (>=2022.11.0)"] 456 | clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] 457 | compression = ["zstandard (>=0.19.0)"] 458 | computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] 459 | consortium-standard = ["dataframe-api-compat (>=0.1.7)"] 460 | 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)"] 461 | feather = ["pyarrow (>=10.0.1)"] 462 | fss = ["fsspec (>=2022.11.0)"] 463 | gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] 464 | hdf5 = ["tables (>=3.8.0)"] 465 | html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] 466 | mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] 467 | output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] 468 | parquet = ["pyarrow (>=10.0.1)"] 469 | performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] 470 | plot = ["matplotlib (>=3.6.3)"] 471 | postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] 472 | pyarrow = ["pyarrow (>=10.0.1)"] 473 | spss = ["pyreadstat (>=1.2.0)"] 474 | sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] 475 | test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] 476 | xml = ["lxml (>=4.9.2)"] 477 | 478 | [[package]] 479 | name = "pathspec" 480 | version = "0.12.1" 481 | description = "Utility library for gitignore style pattern matching of file paths." 482 | optional = false 483 | python-versions = ">=3.8" 484 | files = [ 485 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 486 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 487 | ] 488 | 489 | [[package]] 490 | name = "platformdirs" 491 | version = "4.3.6" 492 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 493 | optional = false 494 | python-versions = ">=3.8" 495 | files = [ 496 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 497 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 498 | ] 499 | 500 | [package.extras] 501 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 502 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 503 | type = ["mypy (>=1.11.2)"] 504 | 505 | [[package]] 506 | name = "pluggy" 507 | version = "1.5.0" 508 | description = "plugin and hook calling mechanisms for python" 509 | optional = false 510 | python-versions = ">=3.8" 511 | files = [ 512 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 513 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 514 | ] 515 | 516 | [package.extras] 517 | dev = ["pre-commit", "tox"] 518 | testing = ["pytest", "pytest-benchmark"] 519 | 520 | [[package]] 521 | name = "pyarrow" 522 | version = "19.0.0" 523 | description = "Python library for Apache Arrow" 524 | optional = false 525 | python-versions = ">=3.9" 526 | files = [ 527 | {file = "pyarrow-19.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:c318eda14f6627966997a7d8c374a87d084a94e4e38e9abbe97395c215830e0c"}, 528 | {file = "pyarrow-19.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:62ef8360ff256e960f57ce0299090fb86423afed5e46f18f1225f960e05aae3d"}, 529 | {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2795064647add0f16563e57e3d294dbfc067b723f0fd82ecd80af56dad15f503"}, 530 | {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a218670b26fb1bc74796458d97bcab072765f9b524f95b2fccad70158feb8b17"}, 531 | {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:66732e39eaa2247996a6b04c8aa33e3503d351831424cdf8d2e9a0582ac54b34"}, 532 | {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e675a3ad4732b92d72e4d24009707e923cab76b0d088e5054914f11a797ebe44"}, 533 | {file = "pyarrow-19.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f094742275586cdd6b1a03655ccff3b24b2610c3af76f810356c4c71d24a2a6c"}, 534 | {file = "pyarrow-19.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8e3a839bf36ec03b4315dc924d36dcde5444a50066f1c10f8290293c0427b46a"}, 535 | {file = "pyarrow-19.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ce42275097512d9e4e4a39aade58ef2b3798a93aa3026566b7892177c266f735"}, 536 | {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9348a0137568c45601b031a8d118275069435f151cbb77e6a08a27e8125f59d4"}, 537 | {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0144a712d990d60f7f42b7a31f0acaccf4c1e43e957f7b1ad58150d6f639c1"}, 538 | {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2a1a109dfda558eb011e5f6385837daffd920d54ca00669f7a11132d0b1e6042"}, 539 | {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:be686bf625aa7b9bada18defb3a3ea3981c1099697239788ff111d87f04cd263"}, 540 | {file = "pyarrow-19.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:239ca66d9a05844bdf5af128861af525e14df3c9591bcc05bac25918e650d3a2"}, 541 | {file = "pyarrow-19.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:a7bbe7109ab6198688b7079cbad5a8c22de4d47c4880d8e4847520a83b0d1b68"}, 542 | {file = "pyarrow-19.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:4624c89d6f777c580e8732c27bb8e77fd1433b89707f17c04af7635dd9638351"}, 543 | {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b6d3ce4288793350dc2d08d1e184fd70631ea22a4ff9ea5c4ff182130249d9b"}, 544 | {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450a7d27e840e4d9a384b5c77199d489b401529e75a3b7a3799d4cd7957f2f9c"}, 545 | {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a08e2a8a039a3f72afb67a6668180f09fddaa38fe0d21f13212b4aba4b5d2451"}, 546 | {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f43f5aef2a13d4d56adadae5720d1fed4c1356c993eda8b59dace4b5983843c1"}, 547 | {file = "pyarrow-19.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f672f5364b2d7829ef7c94be199bb88bf5661dd485e21d2d37de12ccb78a136"}, 548 | {file = "pyarrow-19.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:cf3bf0ce511b833f7bc5f5bb3127ba731e97222023a444b7359f3a22e2a3b463"}, 549 | {file = "pyarrow-19.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:4d8b0c0de0a73df1f1bf439af1b60f273d719d70648e898bc077547649bb8352"}, 550 | {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92aff08e23d281c69835e4a47b80569242a504095ef6a6223c1f6bb8883431d"}, 551 | {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3b78eff5968a1889a0f3bc81ca57e1e19b75f664d9c61a42a604bf9d8402aae"}, 552 | {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b34d3bde38eba66190b215bae441646330f8e9da05c29e4b5dd3e41bde701098"}, 553 | {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5418d4d0fab3a0ed497bad21d17a7973aad336d66ad4932a3f5f7480d4ca0c04"}, 554 | {file = "pyarrow-19.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e82c3d5e44e969c217827b780ed8faf7ac4c53f934ae9238872e749fa531f7c9"}, 555 | {file = "pyarrow-19.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f208c3b58a6df3b239e0bb130e13bc7487ed14f39a9ff357b6415e3f6339b560"}, 556 | {file = "pyarrow-19.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:c751c1c93955b7a84c06794df46f1cec93e18610dcd5ab7d08e89a81df70a849"}, 557 | {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b903afaa5df66d50fc38672ad095806443b05f202c792694f3a604ead7c6ea6e"}, 558 | {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22a4bc0937856263df8b94f2f2781b33dd7f876f787ed746608e06902d691a5"}, 559 | {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:5e8a28b918e2e878c918f6d89137386c06fe577cd08d73a6be8dafb317dc2d73"}, 560 | {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:29cd86c8001a94f768f79440bf83fee23963af5e7bc68ce3a7e5f120e17edf89"}, 561 | {file = "pyarrow-19.0.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:c0423393e4a07ff6fea08feb44153302dd261d0551cc3b538ea7a5dc853af43a"}, 562 | {file = "pyarrow-19.0.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:718947fb6d82409013a74b176bf93e0f49ef952d8a2ecd068fecd192a97885b7"}, 563 | {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1c162c4660e0978411a4761f91113dde8da3433683efa473501254563dcbe8"}, 564 | {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73268cf557e688efb60f1ccbc7376f7e18cd8e2acae9e663e98b194c40c1a2d"}, 565 | {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:edfe6d3916e915ada9acc4e48f6dafca7efdbad2e6283db6fd9385a1b23055f1"}, 566 | {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:da410b70a7ab8eb524112f037a7a35da7128b33d484f7671a264a4c224ac131d"}, 567 | {file = "pyarrow-19.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:597360ffc71fc8cceea1aec1fb60cb510571a744fffc87db33d551d5de919bec"}, 568 | {file = "pyarrow-19.0.0.tar.gz", hash = "sha256:8d47c691765cf497aaeed4954d226568563f1b3b74ff61139f2d77876717084b"}, 569 | ] 570 | 571 | [package.extras] 572 | test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] 573 | 574 | [[package]] 575 | name = "pytest" 576 | version = "8.3.4" 577 | description = "pytest: simple powerful testing with Python" 578 | optional = false 579 | python-versions = ">=3.8" 580 | files = [ 581 | {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, 582 | {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, 583 | ] 584 | 585 | [package.dependencies] 586 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 587 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 588 | iniconfig = "*" 589 | packaging = "*" 590 | pluggy = ">=1.5,<2" 591 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 592 | 593 | [package.extras] 594 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 595 | 596 | [[package]] 597 | name = "pytest-cov" 598 | version = "4.1.0" 599 | description = "Pytest plugin for measuring coverage." 600 | optional = false 601 | python-versions = ">=3.7" 602 | files = [ 603 | {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, 604 | {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, 605 | ] 606 | 607 | [package.dependencies] 608 | coverage = {version = ">=5.2.1", extras = ["toml"]} 609 | pytest = ">=4.6" 610 | 611 | [package.extras] 612 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 613 | 614 | [[package]] 615 | name = "python-dateutil" 616 | version = "2.9.0.post0" 617 | description = "Extensions to the standard Python datetime module" 618 | optional = false 619 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 620 | files = [ 621 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 622 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 623 | ] 624 | 625 | [package.dependencies] 626 | six = ">=1.5" 627 | 628 | [[package]] 629 | name = "python-dotenv" 630 | version = "1.0.1" 631 | description = "Read key-value pairs from a .env file and set them as environment variables" 632 | optional = false 633 | python-versions = ">=3.8" 634 | files = [ 635 | {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, 636 | {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, 637 | ] 638 | 639 | [package.extras] 640 | cli = ["click (>=5.0)"] 641 | 642 | [[package]] 643 | name = "pytz" 644 | version = "2024.2" 645 | description = "World timezone definitions, modern and historical" 646 | optional = false 647 | python-versions = "*" 648 | files = [ 649 | {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, 650 | {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, 651 | ] 652 | 653 | [[package]] 654 | name = "requests" 655 | version = "2.32.3" 656 | description = "Python HTTP for Humans." 657 | optional = false 658 | python-versions = ">=3.8" 659 | files = [ 660 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 661 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 662 | ] 663 | 664 | [package.dependencies] 665 | certifi = ">=2017.4.17" 666 | charset-normalizer = ">=2,<4" 667 | idna = ">=2.5,<4" 668 | urllib3 = ">=1.21.1,<3" 669 | 670 | [package.extras] 671 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 672 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 673 | 674 | [[package]] 675 | name = "six" 676 | version = "1.17.0" 677 | description = "Python 2 and 3 compatibility utilities" 678 | optional = false 679 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 680 | files = [ 681 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, 682 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, 683 | ] 684 | 685 | [[package]] 686 | name = "tomli" 687 | version = "2.2.1" 688 | description = "A lil' TOML parser" 689 | optional = false 690 | python-versions = ">=3.8" 691 | files = [ 692 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 693 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 694 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 695 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 696 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 697 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 698 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 699 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 700 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 701 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 702 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 703 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 704 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 705 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 706 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 707 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 708 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 709 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 710 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 711 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 712 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 713 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 714 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 715 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 716 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 717 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 718 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 719 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 720 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 721 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 722 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 723 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 724 | ] 725 | 726 | [[package]] 727 | name = "tqdm" 728 | version = "4.67.1" 729 | description = "Fast, Extensible Progress Meter" 730 | optional = false 731 | python-versions = ">=3.7" 732 | files = [ 733 | {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, 734 | {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, 735 | ] 736 | 737 | [package.dependencies] 738 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 739 | 740 | [package.extras] 741 | dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] 742 | discord = ["requests"] 743 | notebook = ["ipywidgets (>=6)"] 744 | slack = ["slack-sdk"] 745 | telegram = ["requests"] 746 | 747 | [[package]] 748 | name = "typing-extensions" 749 | version = "4.12.2" 750 | description = "Backported and Experimental Type Hints for Python 3.8+" 751 | optional = false 752 | python-versions = ">=3.8" 753 | files = [ 754 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 755 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 756 | ] 757 | 758 | [[package]] 759 | name = "tzdata" 760 | version = "2024.2" 761 | description = "Provider of IANA time zone data" 762 | optional = false 763 | python-versions = ">=2" 764 | files = [ 765 | {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, 766 | {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, 767 | ] 768 | 769 | [[package]] 770 | name = "urllib3" 771 | version = "2.3.0" 772 | description = "HTTP library with thread-safe connection pooling, file post, and more." 773 | optional = false 774 | python-versions = ">=3.9" 775 | files = [ 776 | {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, 777 | {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, 778 | ] 779 | 780 | [package.extras] 781 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 782 | h2 = ["h2 (>=4,<5)"] 783 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 784 | zstd = ["zstandard (>=0.18.0)"] 785 | 786 | [metadata] 787 | lock-version = "2.0" 788 | python-versions = ">=3.9,<3.13" 789 | content-hash = "83cf594a0ec27225fbf118b14f9357a69890c6d68cc21f2c153647aee0fd78f5" 790 | --------------------------------------------------------------------------------