diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0f865d09..12107bfc7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,16 @@ Changelog 0.25 ==== +0.25.2 +------ +Added +^^^^^ +- Add Dameng database backend support with `dm://` URL scheme +- Add enterprise-grade connection pool for Dameng with health checks and connection reuse +- Add complete type mapping between Tortoise ORM and Dameng database types +- Add parameter conversion from `:1` format to `?` format for Dameng compatibility +- Enhance Sanic contrib module with exception handlers and timezone support + 0.25.1 ------------------ Changed diff --git a/Makefile b/Makefile index 32fa29bb5..523a4af2f 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ TORTOISE_MYSQL_PASS ?= 123456 TORTOISE_POSTGRES_PASS ?= 123456 TORTOISE_MSSQL_PASS ?= 123456 TORTOISE_ORACLE_PASS ?= 123456 +TORTOISE_DAMENG_PASS ?= SYSDBA001 help: @echo "Tortoise ORM development makefile" @@ -24,10 +25,10 @@ up: @poetry update deps: - @poetry install --all-groups -E asyncpg -E accel -E psycopg -E asyncodbc -E aiomysql + @poetry install --all-groups -E asyncpg -E accel -E psycopg -E asyncodbc -E aiomysql -E dameng deps_with_asyncmy: - @poetry install --all-groups -E asyncpg -E accel -E psycopg -E asyncodbc -E asyncmy + @poetry install --all-groups -E asyncpg -E accel -E psycopg -E asyncodbc -E asyncmy -E dameng check: build _check _check: @@ -85,7 +86,10 @@ test_mssql: test_oracle: $(py_warn) TORTOISE_TEST_DB="oracle://SYSTEM:$(TORTOISE_ORACLE_PASS)@127.0.0.1:1521/test_\{\}?driver=$(TORTOISE_ORACLE_DRIVER)" pytest $(pytest_opts) --cov-report= -_testall: test_sqlite test_postgres_asyncpg test_postgres_psycopg test_mysql_myisam test_mysql test_mysql_asyncmy test_mssql +test_dameng: + $(py_warn) TORTOISE_TEST_DB="dm://SYSDBA:$(TORTOISE_DAMENG_PASS)@127.0.0.1:5236/test_\{\}" pytest $(pytest_opts) --cov-report= + +_testall: test_sqlite test_postgres_asyncpg test_postgres_psycopg test_mysql_myisam test_mysql test_mysql_asyncmy test_mssql test_dameng coverage report diff --git a/README.rst b/README.rst index 6c4ba455a..59209dda1 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ You can find the docs at `Documentation `_ Tortoise ORM is a young project and breaking changes are to be expected. We keep a `Changelog `_ and it will have possible breakage clearly documented. -Tortoise ORM supports CPython 3.9 and later for SQLite, MySQL, PostgreSQL, Microsoft SQL Server, and Oracle. +Tortoise ORM supports CPython 3.9 and later for SQLite, MySQL, PostgreSQL, Microsoft SQL Server, Oracle, and Dameng. Why was Tortoise ORM built? --------------------------- @@ -76,6 +76,8 @@ The following table shows the available installation options for different datab - ``pip install tortoise-orm[asyncodbc]`` * - Oracle - ``pip install tortoise-orm[asyncodbc]`` + * - Dameng + - ``pip install tortoise-orm[dameng]`` Quick Tutorial diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index b28760603..bcafd917e 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -54,7 +54,7 @@ The code is structured in the following directories: ``tortoise/fields/``: The Fields are defined here. ``tortoise/backends/``: - DB Backends, such as ``sqlite``, ``asyncpg``, ``psycopg`` & ``mysql`` + DB Backends, such as ``sqlite``, ``asyncpg``, ``psycopg``, ``mysql`` & ``dameng`` ``tortoise/backends/base/``: Common DB Backend code ``tortoise/contrib/``: @@ -113,7 +113,8 @@ Different types of tests - ``make test_postgres_psycopg``: Runs the psycopg tests on the postgres database - ``make test_mysql_myisam``: Runs the tests on the mysql database using the ``MYISAM`` storage engine (no transactions) - ``make test_mysql``: Runs the tests on the mysql database -- ``make testall``: runs the tests on all 4 database types: sqlite (in memory), postgresql, MySQL-MyISAM and MySQL-InnoDB +- ``make test_dameng``: Runs the tests on the dameng database +- ``make testall``: runs the tests on all 5 database types: sqlite (in memory), postgresql, MySQL-MyISAM, MySQL-InnoDB and Dameng - ``green``: runs the same tests as ``make test``, ensures the green plugin works diff --git a/docs/contrib.rst b/docs/contrib.rst index e4e46a493..ee599900f 100644 --- a/docs/contrib.rst +++ b/docs/contrib.rst @@ -14,4 +14,5 @@ Contrib contrib/aiohttp contrib/mysql contrib/postgres + contrib/dameng contrib/blacksheep diff --git a/docs/contrib/dameng.rst b/docs/contrib/dameng.rst new file mode 100644 index 000000000..f58bb1ba8 --- /dev/null +++ b/docs/contrib/dameng.rst @@ -0,0 +1,340 @@ +.. _contrib_dameng: + +===================== +Dameng-specific Features +===================== + +This module contains Dameng-specific features for Tortoise ORM. + +Connection String +================= + +Dameng database connection strings should use the ``dm://`` URL scheme: + +.. code-block:: python3 + + # Basic connection + 'dm://username:password@host:port/database' + + # With charset + 'dm://username:password@host:port/database?charset=utf8' + + # Default port is 5236 + 'dm://username:password@host/database' + +Fields +====== + +Dameng-specific field types provide native support for Dameng database features. + +XMLField +-------- + +.. autoclass:: tortoise.contrib.dameng.fields.XMLField + :members: + +Store and retrieve XML data: + +.. code-block:: python3 + + from tortoise.models import Model + from tortoise.contrib.dameng.fields import XMLField + + class Document(Model): + id = fields.IntField(primary_key=True) + content = XMLField() + + # Usage + doc = await Document.create( + content='value' + ) + +IntervalField +------------- + +.. autoclass:: tortoise.contrib.dameng.fields.IntervalField + :members: + +Store time intervals: + +.. code-block:: python3 + + from tortoise.models import Model + from tortoise.contrib.dameng.fields import IntervalField + + class Task(Model): + id = fields.IntField(primary_key=True) + duration = IntervalField() + + # Usage + task = await Task.create( + duration=timedelta(hours=2, minutes=30) + ) + +RowIDField +---------- + +.. autoclass:: tortoise.contrib.dameng.fields.RowIDField + :members: + +Access Dameng's internal ROWID: + +.. code-block:: python3 + + from tortoise.models import Model + from tortoise.contrib.dameng.fields import RowIDField + + class Record(Model): + id = fields.IntField(primary_key=True) + row_id = RowIDField() + +Functions +========= + +Dameng-specific SQL functions for use in queries. + +Date/Time Functions +------------------- + +.. autoclass:: tortoise.contrib.dameng.functions.ToChar + :members: + +Convert date/time to string: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import ToChar + + # Get formatted date + await Model.annotate( + date_str=ToChar('created_at', 'YYYY-MM-DD') + ).values('date_str') + +.. autoclass:: tortoise.contrib.dameng.functions.ToDate + :members: + +Convert string to date: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import ToDate + + # Parse date string + await Model.filter( + created_at__gte=ToDate('2024-01-01', 'YYYY-MM-DD') + ) + +.. autoclass:: tortoise.contrib.dameng.functions.SysDate + :members: + +Get current system date: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import SysDate + + # Filter by current date + await Model.filter( + created_at__lte=SysDate() + ) + +String Functions +---------------- + +.. autoclass:: tortoise.contrib.dameng.functions.InitCap + :members: + +Capitalize first letter of each word: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import InitCap + + await Model.annotate( + title_case=InitCap('name') + ).values('title_case') + +.. autoclass:: tortoise.contrib.dameng.functions.Instr + :members: + +Find substring position: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import Instr + + # Find position of '@' in email + await Model.annotate( + at_pos=Instr('email', '@') + ).values('at_pos') + +.. autoclass:: tortoise.contrib.dameng.functions.LPad + :members: + +Left-pad string: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import LPad + + # Pad ID with zeros + await Model.annotate( + padded_id=LPad('id', 5, '0') + ).values('padded_id') + +.. autoclass:: tortoise.contrib.dameng.functions.RPad + :members: + +Right-pad string: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import RPad + + # Pad name with spaces + await Model.annotate( + padded_name=RPad('name', 20, ' ') + ).values('padded_name') + +Utility Functions +----------------- + +.. autoclass:: tortoise.contrib.dameng.functions.NVL + :members: + +Replace NULL values: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import NVL + + # Replace NULL with default + await Model.annotate( + status=NVL('status', 'active') + ).values('status') + +.. autoclass:: tortoise.contrib.dameng.functions.Decode + :members: + +SQL CASE expression: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import Decode + + # Map values + await Model.annotate( + category=Decode( + 'type', + 1, 'Basic', + 2, 'Premium', + 3, 'Enterprise', + 'Unknown' + ) + ).values('category') + +.. autoclass:: tortoise.contrib.dameng.functions.RowNum + :members: + +Get row number: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import RowNum + + # Get first 10 rows + await Model.filter( + RowNum() <= 10 + ).values() + +Aggregate Functions +------------------- + +.. autoclass:: tortoise.contrib.dameng.functions.ListAgg + :members: + +Aggregate values into string: + +.. code-block:: python3 + + from tortoise.contrib.dameng.functions import ListAgg + + # Concatenate names + await Model.annotate( + all_names=ListAgg('name', ',') + ).group_by('department') + +Performance Considerations +========================== + +Connection Pooling +------------------ + +The Dameng backend includes an enterprise-grade connection pool with: + +- Automatic connection health checks +- Connection reuse and recycling +- Configurable pool size (min/max connections) +- Idle timeout management +- Automatic reconnection on failure + +Configure connection pooling: + +.. code-block:: python3 + + await Tortoise.init( + db_url='dm://user:pass@host:5236/db', + modules={'models': ['app.models']}, + # Connection pool settings + minsize=1, + maxsize=5, + connection_timeout=10, + pool_recycle=3600, # Recycle connections after 1 hour + ) + +Parameter Binding +----------------- + +The Dameng backend automatically converts Tortoise's parameter placeholders +(`:1`, `:2`, etc.) to Dameng's native format (`?`). This ensures compatibility +while maintaining security through proper parameter binding. + +Thread Safety +------------- + +Since dmPython driver operations are synchronous, the Dameng backend uses +ThreadPoolExecutor to run database operations without blocking the async event +loop. This ensures thread safety while maintaining async compatibility. + +Limitations +=========== + +- Dameng does not support certain PostgreSQL-specific features like ARRAY fields +- Some advanced JSON operations may have limited support +- Full-text search capabilities differ from PostgreSQL's implementation +- Spatial/GIS features require Dameng's spatial extension + +Migration Notes +=============== + +When migrating from other databases to Dameng: + +1. **Data Types**: Review field mappings as some types may differ +2. **SQL Syntax**: Some queries may need adjustment for Dameng-specific syntax +3. **Functions**: Use Dameng-specific functions from this contrib module +4. **Indexes**: Review index strategies as Dameng may have different optimization patterns + +Example Migration +----------------- + +.. code-block:: python3 + + # PostgreSQL model + class OldModel(Model): + data = fields.JSONField() + tags = ArrayField() # Not supported in Dameng + + # Dameng model + class NewModel(Model): + data = fields.JSONField() + tags = fields.TextField() # Store as JSON string instead \ No newline at end of file diff --git a/docs/databases.rst b/docs/databases.rst index 8e59107eb..86f9a5aa2 100644 --- a/docs/databases.rst +++ b/docs/databases.rst @@ -10,6 +10,7 @@ Tortoise currently supports the following databases: * PostgreSQL >= 9.4 (using ``asyncpg`` or ``psycopg``) * MySQL/MariaDB (using ``asyncmy`` or ``aiomysql``) * Microsoft SQL Server (using ``asyncodbc``) +* Dameng (using ``dameng``) To use, please ensure that corresponding asyncio driver is installed. @@ -47,6 +48,8 @@ The supported ``DB_TYPE``: Typically in the form of :samp:`mysql://myuser:mypass@db.host:3306/somedb` ``mssql``: Typically in the form of :samp:`mssql://myuser:mypass@db.host:1433/somedb?driver=the odbc driver` +``dm``: + Typically in the form of :samp:`dm://SYSDBA:password@db.host:5236/somedb` Capabilities ============ @@ -213,6 +216,55 @@ Encoding in Oracle: If you get ``???`` values in Varchar fields instead of your actual text (russian/chinese/etc), then set ``NLS_LANG`` variable in your client environment to support UTF8. For example, `"American_America.UTF8"`. +Dameng +====== + +Dameng is a Chinese enterprise database system. Tortoise ORM provides native support for Dameng through the ``dameng`` driver. + +DB URL is typically in the form of :samp:`dm://SYSDBA:password@db.host:5236/somedb` + +Required Parameters +------------------- + +``user``: + Username to connect with (default is ``SYSDBA``). +``password``: + Password for username. +``host``: + Network host that database is available at. +``port``: + Network port that database is available at. (defaults to ``5236``) +``database``: + Database to use. + +Optional parameters: +-------------------- + +``charset`` (defaults to ``utf8``): + Character set to use. +``connect_timeout`` (defaults to ``10``): + Connection timeout in seconds. +``pool_min_size`` (defaults to ``1``): + Minimum connection pool size. +``pool_max_size`` (defaults to ``10``): + Maximum connection pool size. +``pool_max_idle_time`` (defaults to ``300``): + Maximum idle time for connections in seconds. +``pool_acquire_timeout`` (defaults to ``30``): + Timeout for acquiring a connection from the pool in seconds. + +Connection Pool Features: +------------------------ + +The Dameng backend includes an enterprise-grade connection pool with: + +* Automatic connection health checks +* Connection reuse and recycling +* Idle timeout management +* Automatic recovery from connection failures +* Thread-safe operations using ThreadPoolExecutor + + Passing in custom SSL Certificates ================================== diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 0ada1c301..16b72e0df 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -28,6 +28,8 @@ The following table shows the available installation options for different datab - ``pip install "tortoise-orm[asyncodbc]"`` * - Oracle - ``pip install "tortoise-orm[asyncodbc]"`` + * - Dameng + - ``pip install "tortoise-orm[dameng]"`` Optional Dependencies diff --git a/docs/index.rst b/docs/index.rst index df24512af..fb82b798e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ Tortoise ORM is an easy-to-use ``asyncio`` ORM *(Object Relational Mapper)* insp Source & issue trackers are available at ``_ -Tortoise ORM supports CPython 3.9 and later for SQLite, MySQL, PostgreSQL, Microsoft SQL Server, and Oracle. +Tortoise ORM supports CPython 3.9 and later for SQLite, MySQL, PostgreSQL, Microsoft SQL Server, Oracle, and Dameng. Introduction ============ diff --git a/docs/roadmap.rst b/docs/roadmap.rst index f3a2383c2..42c188ff6 100644 --- a/docs/roadmap.rst +++ b/docs/roadmap.rst @@ -38,6 +38,7 @@ Here we have all the features that is slightly further out, in no particular ord * Extra DB support * CockroachDB * Firebird + * [done] Dameng * Enhanced test support * ``hypothesis`` strategy builder diff --git a/pyproject.toml b/pyproject.toml index 4ed019abd..cc3e8bb9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Easy async ORM for python, built with relations in mind" authors = [{name="Andrey Bondar", email="andrey@bondar.ru"}, {name="Nickolas Grigoriadis", email="nagrigoriadis@gmail.com"}, {name="long2ice", email="long2ice@gmail.com"}] license = {text="Apache-2.0"} readme = "README.rst" -keywords = ["sql", "mysql", "postgres", "psql", "sqlite", "aiosqlite", "asyncpg", "relational", "database", "rdbms", "orm", "object mapper", "async", "asyncio", "aio", "psycopg"] +keywords = ["sql", "mysql", "postgres", "psql", "sqlite", "aiosqlite", "asyncpg", "relational", "database", "rdbms", "orm", "object mapper", "async", "asyncio", "aio", "psycopg", "dameng", "dmpython"] include = ["CHANGELOG.rst", "LICENSE", "README.rst"] # classifieres is dynamic because poetry will create Python classifiers automatically with value in `tool.poetry` section dynamic = [ "classifiers" ] @@ -15,6 +15,8 @@ dependencies = [ "iso8601 (>=2.1.0,<3.0.0); python_version < '4.0'", "aiosqlite (>=0.16.0,<1.0.0)", "pytz", + "dmpython>=2.5.22", + "pydantic>=2.11.7", ] [project.optional-dependencies] @@ -28,6 +30,7 @@ aiomysql = ["aiomysql"] asyncmy = ["asyncmy (>=0.2.8,<1.0.0); python_version < '4.0'"] psycopg = ["psycopg[pool,binary] (>=3.0.12,<4.0.0)"] asyncodbc = ["asyncodbc (>=0.1.1,<1.0.0); python_version < '4.0'"] +dameng = ["dmPython (>=2.5.22,<3.0.0)"] [project.urls] documentation = "https://tortoise.github.io" @@ -113,6 +116,14 @@ docutils = "*" requires = ["poetry-core>=2.0.0"] build-backend = "poetry.core.masonry.api" +[dependency-groups] +dev = [ + "pypika>=0.48.9", + "pytest>=8.4.1", + "pytest-asyncio>=1.1.0", + "pytest-cov>=6.2.1", +] + [tool.mypy] pretty = true exclude = ["docs"] diff --git a/tests/backends/dameng/test_executor.py b/tests/backends/dameng/test_executor.py new file mode 100644 index 000000000..577862ca9 --- /dev/null +++ b/tests/backends/dameng/test_executor.py @@ -0,0 +1,222 @@ +""" +Test Dameng SQL executor +""" + +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from tortoise.backends.dameng.executor import DmExecutor + + +class TestDmExecutor(unittest.TestCase): + """Test Dameng SQL executor""" + + def setUp(self): + """Set up test fixtures""" + self.connection = MagicMock() + self.executor = DmExecutor(self.connection, None) + + def test_parameter_conversion(self): + """Test parameter placeholder conversion from :1 to ?""" + # Simple case + sql = "SELECT * FROM users WHERE id = :1" + converted = self.executor._convert_parameters(sql) + self.assertEqual(converted, "SELECT * FROM users WHERE id = ?") + + # Multiple parameters + sql = "SELECT * FROM users WHERE id = :1 AND name = :2" + converted = self.executor._convert_parameters(sql) + self.assertEqual(converted, "SELECT * FROM users WHERE id = ? AND name = ?") + + # Out of order parameters + sql = "SELECT * FROM users WHERE name = :2 AND id = :1" + converted = self.executor._convert_parameters(sql) + self.assertEqual(converted, "SELECT * FROM users WHERE name = ? AND id = ?") + + # Parameters in complex query + sql = """ + INSERT INTO users (id, name, email) + VALUES (:1, :2, :3) + """ + converted = self.executor._convert_parameters(sql) + expected = """ + INSERT INTO users (id, name, email) + VALUES (?, ?, ?) + """ + self.assertEqual(converted, expected) + + def test_parameter_reordering(self): + """Test parameter value reordering""" + # In order parameters + sql = "SELECT * FROM users WHERE id = :1 AND name = :2" + values = [1, "John"] + new_sql, new_values = self.executor._reorder_parameters(sql, values) + self.assertEqual(new_sql, "SELECT * FROM users WHERE id = ? AND name = ?") + self.assertEqual(new_values, [1, "John"]) + + # Out of order parameters + sql = "SELECT * FROM users WHERE name = :2 AND id = :1" + values = [1, "John"] + new_sql, new_values = self.executor._reorder_parameters(sql, values) + self.assertEqual(new_sql, "SELECT * FROM users WHERE name = ? AND id = ?") + self.assertEqual(new_values, ["John", 1]) + + # Repeated parameters + sql = "SELECT * FROM users WHERE id = :1 OR parent_id = :1" + values = [1] + new_sql, new_values = self.executor._reorder_parameters(sql, values) + self.assertEqual(new_sql, "SELECT * FROM users WHERE id = ? OR parent_id = ?") + self.assertEqual(new_values, [1, 1]) + + def test_no_parameters(self): + """Test queries without parameters""" + sql = "SELECT * FROM users" + converted = self.executor._convert_parameters(sql) + self.assertEqual(converted, sql) + + new_sql, new_values = self.executor._reorder_parameters(sql, []) + self.assertEqual(new_sql, sql) + self.assertEqual(new_values, []) + + async def test_execute_insert(self): + """Test execute_insert method""" + cursor = MagicMock() + cursor.execute = MagicMock() + cursor.lastrowid = 123 + + with patch.object(self.executor, 'acquire_cursor') as mock_acquire: + mock_acquire.return_value.__aenter__.return_value = cursor + + sql = "INSERT INTO users (name) VALUES (:1)" + result = await self.executor.execute_insert(sql, ["John"]) + + cursor.execute.assert_called_once_with( + "INSERT INTO users (name) VALUES (?)", + ["John"] + ) + self.assertEqual(result, 123) + + async def test_execute_many(self): + """Test execute_many method""" + cursor = MagicMock() + cursor.executemany = MagicMock() + + with patch.object(self.executor, 'acquire_cursor') as mock_acquire: + mock_acquire.return_value.__aenter__.return_value = cursor + + sql = "INSERT INTO users (name, age) VALUES (:1, :2)" + values = [["John", 25], ["Jane", 30]] + + await self.executor.execute_many(sql, values) + + cursor.executemany.assert_called_once_with( + "INSERT INTO users (name, age) VALUES (?, ?)", + [["John", 25], ["Jane", 30]] + ) + + async def test_execute_query(self): + """Test execute_query method""" + cursor = MagicMock() + cursor.execute = MagicMock() + cursor.fetchall = MagicMock(return_value=[ + (1, "John", 25), + (2, "Jane", 30) + ]) + cursor.description = [ + ("id",), ("name",), ("age",) + ] + + with patch.object(self.executor, 'acquire_cursor') as mock_acquire: + mock_acquire.return_value.__aenter__.return_value = cursor + + sql = "SELECT * FROM users WHERE age > :1" + result = await self.executor.execute_query(sql, [20]) + + cursor.execute.assert_called_once_with( + "SELECT * FROM users WHERE age > ?", + [20] + ) + + # Result should be list of tuples + self.assertEqual(len(result), 2) + self.assertEqual(result[0], (1, "John", 25)) + self.assertEqual(result[1], (2, "Jane", 30)) + + async def test_execute_query_dict(self): + """Test execute_query_dict method""" + cursor = MagicMock() + cursor.execute = MagicMock() + cursor.fetchall = MagicMock(return_value=[ + (1, "John", 25), + (2, "Jane", 30) + ]) + cursor.description = [ + ("id",), ("name",), ("age",) + ] + + with patch.object(self.executor, 'acquire_cursor') as mock_acquire: + mock_acquire.return_value.__aenter__.return_value = cursor + + sql = "SELECT * FROM users WHERE age > :1" + result = await self.executor.execute_query_dict(sql, [20]) + + cursor.execute.assert_called_once_with( + "SELECT * FROM users WHERE age > ?", + [20] + ) + + # Result should be list of dicts + self.assertEqual(len(result), 2) + self.assertEqual(result[0], {"id": 1, "name": "John", "age": 25}) + self.assertEqual(result[1], {"id": 2, "name": "Jane", "age": 30}) + + def test_complex_parameter_patterns(self): + """Test complex parameter patterns""" + # Parameters in subqueries + sql = """ + SELECT * FROM users + WHERE id IN (SELECT user_id FROM orders WHERE total > :1) + AND created_at > :2 + """ + converted = self.executor._convert_parameters(sql) + expected = """ + SELECT * FROM users + WHERE id IN (SELECT user_id FROM orders WHERE total > ?) + AND created_at > ? + """ + self.assertEqual(converted, expected) + + # Parameters with similar numbers + sql = "SELECT * FROM table WHERE col1 = :1 AND col12 = :12 AND col2 = :2" + values = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"] + new_sql, new_values = self.executor._reorder_parameters(sql, values) + self.assertEqual( + new_sql, + "SELECT * FROM table WHERE col1 = ? AND col12 = ? AND col2 = ?" + ) + self.assertEqual(new_values, ["a", "l", "b"]) + + def test_edge_cases(self): + """Test edge cases in parameter conversion""" + # Empty SQL + sql = "" + converted = self.executor._convert_parameters(sql) + self.assertEqual(converted, "") + + # SQL with no spaces around parameters + sql = "WHERE id=:1AND name=:2" + converted = self.executor._convert_parameters(sql) + self.assertEqual(converted, "WHERE id=?AND name=?") + + # Parameters at the end + sql = "WHERE id = :1" + converted = self.executor._convert_parameters(sql) + self.assertEqual(converted, "WHERE id = ?") + + def test_parameter_boundary(self): + """Test parameter boundary detection""" + # Ensure :1 in col:1 is replaced but not in col:1name + sql = "SELECT col:1, col:1name, :1 FROM table" + converted = self.executor._convert_parameters(sql) + # Only the standalone :1 should be replaced + self.assertEqual(converted, "SELECT col:1, col:1name, ? FROM table") \ No newline at end of file diff --git a/tests/backends/dameng/test_pool.py b/tests/backends/dameng/test_pool.py new file mode 100644 index 000000000..590122c6f --- /dev/null +++ b/tests/backends/dameng/test_pool.py @@ -0,0 +1,224 @@ +""" +Test Dameng connection pool +""" + +import asyncio +import unittest +from datetime import datetime, timedelta +from unittest.mock import AsyncMock, MagicMock, patch + +from tortoise.backends.dameng.pool import DmConnectionPool + + +class TestDmConnectionPool(unittest.TestCase): + """Test Dameng connection pool implementation""" + + def setUp(self): + """Set up test fixtures""" + self.pool_config = { + "host": "localhost", + "port": 5236, + "user": "SYSDBA", + "password": "SYSDBA", + "database": "DAMENG", + "minsize": 1, + "maxsize": 5, + } + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_pool_initialization(self, mock_dm): + """Test pool initialization""" + pool = DmConnectionPool(**self.pool_config) + + self.assertEqual(pool.minsize, 1) + self.assertEqual(pool.maxsize, 5) + self.assertEqual(pool._size, 0) + self.assertFalse(pool._initialized) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_pool_init(self, mock_dm): + """Test pool init creates minimum connections""" + mock_conn = MagicMock() + mock_dm.connect.return_value = mock_conn + + pool = DmConnectionPool(**self.pool_config) + await pool._init() + + # Should create minsize connections + self.assertEqual(mock_dm.connect.call_count, 1) + self.assertTrue(pool._initialized) + self.assertEqual(pool._size, 1) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_acquire_connection(self, mock_dm): + """Test acquiring a connection""" + mock_conn = MagicMock() + mock_dm.connect.return_value = mock_conn + + pool = DmConnectionPool(**self.pool_config) + await pool._init() + + # Acquire connection + async with pool.acquire() as conn: + self.assertEqual(conn, mock_conn) + self.assertEqual(pool._used_connections, 1) + + # After context exit, connection should be released + self.assertEqual(pool._used_connections, 0) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_connection_health_check(self, mock_dm): + """Test connection health checking""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_conn.cursor.return_value = mock_cursor + mock_dm.connect.return_value = mock_conn + + pool = DmConnectionPool(**self.pool_config) + + # Test healthy connection + mock_cursor.execute.return_value = None + result = await pool._check_connection_health(mock_conn) + self.assertTrue(result) + mock_cursor.execute.assert_called_with("SELECT 1 FROM DUAL") + + # Test unhealthy connection + mock_cursor.execute.side_effect = Exception("Connection lost") + result = await pool._check_connection_health(mock_conn) + self.assertFalse(result) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_connection_recycling(self, mock_dm): + """Test connection recycling based on age""" + mock_conn = MagicMock() + mock_dm.connect.return_value = mock_conn + + pool = DmConnectionPool(**self.pool_config, pool_recycle=1) # 1 second recycle + await pool._init() + + # Get connection info + conn_wrapper = pool._queue.get_nowait() + + # Make connection old + conn_wrapper.created_at = datetime.now() - timedelta(seconds=2) + + # Put back and try to acquire - should create new connection + pool._queue.put_nowait(conn_wrapper) + + async with pool.acquire() as conn: + pass + + # Should have closed old connection + mock_conn.close.assert_called() + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_pool_close(self, mock_dm): + """Test closing the pool""" + mock_conn1 = MagicMock() + mock_conn2 = MagicMock() + mock_dm.connect.side_effect = [mock_conn1, mock_conn2] + + pool = DmConnectionPool(**self.pool_config, minsize=2) + await pool._init() + + # Close pool + await pool.close() + + # All connections should be closed + mock_conn1.close.assert_called_once() + mock_conn2.close.assert_called_once() + self.assertEqual(pool._size, 0) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_concurrent_acquire(self, mock_dm): + """Test concurrent connection acquisition""" + connections = [] + for i in range(5): + mock_conn = MagicMock() + mock_conn.id = i + connections.append(mock_conn) + + mock_dm.connect.side_effect = connections + + pool = DmConnectionPool(**self.pool_config, minsize=1, maxsize=3) + await pool._init() + + # Try to acquire more connections than maxsize + acquired = [] + + async def acquire_conn(): + async with pool.acquire() as conn: + acquired.append(conn) + await asyncio.sleep(0.1) + + # This should work for first 3 + tasks = [acquire_conn() for _ in range(3)] + await asyncio.gather(*tasks) + + self.assertEqual(len(acquired), 3) + self.assertEqual(pool._size, 3) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_connection_timeout(self, mock_dm): + """Test connection acquisition timeout""" + mock_conn = MagicMock() + mock_dm.connect.return_value = mock_conn + + pool = DmConnectionPool(**self.pool_config, minsize=1, maxsize=1) + await pool._init() + + # Hold one connection + conn1 = await pool.acquire() + + # Try to acquire another with timeout + with self.assertRaises(asyncio.TimeoutError): + await asyncio.wait_for(pool.acquire(), timeout=0.1) + + # Release first connection + await pool.release(conn1) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_failed_connection_creation(self, mock_dm): + """Test handling of failed connection creation""" + mock_dm.connect.side_effect = Exception("Connection failed") + + pool = DmConnectionPool(**self.pool_config) + + with self.assertRaises(Exception): + await pool._init() + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_connection_wrapper_properties(self, mock_dm): + """Test ConnectionWrapper properties""" + mock_conn = MagicMock() + mock_dm.connect.return_value = mock_conn + + pool = DmConnectionPool(**self.pool_config) + wrapper = pool.ConnectionWrapper(mock_conn) + + self.assertEqual(wrapper.connection, mock_conn) + self.assertIsInstance(wrapper.created_at, datetime) + self.assertIsInstance(wrapper.last_used, datetime) + self.assertEqual(wrapper.use_count, 0) + + @patch("tortoise.backends.dameng.pool.dmPython") + async def test_idle_connection_cleanup(self, mock_dm): + """Test cleanup of idle connections""" + mock_conn = MagicMock() + mock_dm.connect.return_value = mock_conn + + # Create pool with idle timeout + pool = DmConnectionPool(**self.pool_config, idle_timeout=1) + await pool._init() + + # Get connection wrapper and make it idle + conn_wrapper = pool._queue.get_nowait() + conn_wrapper.last_used = datetime.now() - timedelta(seconds=2) + pool._queue.put_nowait(conn_wrapper) + + # Run cleanup + await pool._cleanup_idle_connections() + + # Connection should be closed and removed + mock_conn.close.assert_called_once() + self.assertEqual(pool._size, 0) \ No newline at end of file diff --git a/tests/backends/dameng/test_schema.py b/tests/backends/dameng/test_schema.py new file mode 100644 index 000000000..a5f4bef15 --- /dev/null +++ b/tests/backends/dameng/test_schema.py @@ -0,0 +1,199 @@ +""" +Test Dameng schema generation +""" + +import unittest + +from tortoise import fields +from tortoise.backends.dameng.schema_generator import DmSchemaGenerator +from tortoise.models import Model + + +class TestModel(Model): + id = fields.IntField(primary_key=True) + name = fields.CharField(max_length=50) + email = fields.CharField(max_length=100, unique=True) + age = fields.IntField(null=True) + balance = fields.DecimalField(max_digits=10, decimal_places=2) + is_active = fields.BooleanField(default=True) + created_at = fields.DatetimeField(auto_now_add=True) + + class Meta: + app = "test" + table = "test_model" + indexes = [ + ["name", "age"], + ] + + +class TestDmSchemaGenerator(unittest.TestCase): + """Test Dameng schema generator""" + + def setUp(self): + """Set up test fixtures""" + self.generator = DmSchemaGenerator(None) + + def test_quote(self): + """Test identifier quoting""" + self.assertEqual(self.generator.quote("table"), '"table"') + self.assertEqual(self.generator.quote("Table"), '"Table"') + self.assertEqual(self.generator.quote("my_table"), '"my_table"') + + def test_column_comment_generator(self): + """Test column comment generation""" + comment = self.generator._column_comment_generator( + "table", + "column", + "Test comment", + {} + ) + expected = 'COMMENT ON COLUMN "table"."column" IS \'Test comment\'' + self.assertEqual(comment, expected) + + def test_table_comment_generator(self): + """Test table comment generation""" + comment = self.generator._table_comment_generator( + "table", + "Test table comment", + {} + ) + expected = 'COMMENT ON TABLE "table" IS \'Test table comment\'' + self.assertEqual(comment, expected) + + def test_column_default_generator(self): + """Test column default value generation""" + # Boolean default + default = self.generator._column_default_generator( + "table", + "is_active", + {"default": True}, + True + ) + self.assertEqual(default, "1") + + # String default + default = self.generator._column_default_generator( + "table", + "status", + {"default": "active"}, + False + ) + self.assertEqual(default, "'active'") + + # Numeric default + default = self.generator._column_default_generator( + "table", + "count", + {"default": 0}, + False + ) + self.assertEqual(default, "0") + + def test_escape_default_value(self): + """Test default value escaping""" + # Boolean values + self.assertEqual(self.generator._escape_default_value(True, True), "1") + self.assertEqual(self.generator._escape_default_value(False, True), "0") + + # String values + self.assertEqual( + self.generator._escape_default_value("test", False), + "'test'" + ) + self.assertEqual( + self.generator._escape_default_value("test's", False), + "'test''s'" + ) + + # Numeric values + self.assertEqual(self.generator._escape_default_value(42, False), "42") + self.assertEqual(self.generator._escape_default_value(3.14, False), "3.14") + + def test_get_create_sequence_sql(self): + """Test sequence creation SQL""" + sql = self.generator._get_create_sequence_sql("test_seq") + expected = 'CREATE SEQUENCE "test_seq"' + self.assertEqual(sql, expected) + + def test_create_fk_string(self): + """Test foreign key creation string""" + fk_str = self.generator._create_fk_string( + "orders", + "customer_id", + "customers", + "id", + "CASCADE", + "SET NULL", + "FK_orders_customers" + ) + expected = ( + 'ALTER TABLE "orders" ADD CONSTRAINT "FK_orders_customers" ' + 'FOREIGN KEY ("customer_id") REFERENCES "customers" ("id") ' + 'ON DELETE CASCADE ON UPDATE SET NULL' + ) + self.assertEqual(fk_str, expected) + + def test_create_index_sql(self): + """Test index creation SQL""" + # Single column index + sql = self.generator._create_index_sql( + "test_table", + ["name"], + False, + "idx_name" + ) + expected = 'CREATE INDEX "idx_name" ON "test_table" ("name")' + self.assertEqual(sql, expected) + + # Multi-column index + sql = self.generator._create_index_sql( + "test_table", + ["name", "age"], + False, + "idx_name_age" + ) + expected = 'CREATE INDEX "idx_name_age" ON "test_table" ("name", "age")' + self.assertEqual(sql, expected) + + # Unique index + sql = self.generator._create_index_sql( + "test_table", + ["email"], + True, + "idx_email_unique" + ) + expected = 'CREATE UNIQUE INDEX "idx_email_unique" ON "test_table" ("email")' + self.assertEqual(sql, expected) + + def test_get_table_sql(self): + """Test table creation SQL generation""" + # This would require setting up the full model metadata + # For now, test that the method exists and returns expected structure + self.assertTrue(hasattr(self.generator, "_get_table_sql")) + + def test_boolean_field_default(self): + """Test boolean field default values""" + # Test that boolean fields get proper defaults + field = fields.BooleanField(default=True) + db_field = field.to_db_value(True, None) + self.assertEqual(db_field, True) + + def test_index_name_generation(self): + """Test automatic index name generation""" + # Index names should be properly formatted + table_name = "test_table" + columns = ["col1", "col2"] + + # The generator should handle index naming + self.assertTrue(hasattr(self.generator, "_create_index_sql")) + + def test_field_type_mapping(self): + """Test that field types are properly mapped""" + # This tests the integration with the types module + from tortoise.backends.dameng.types import TO_DB_OVERRIDE + + # Check some common field mappings + self.assertIn("BIG_INT", TO_DB_OVERRIDE) + self.assertIn("VARCHAR2", TO_DB_OVERRIDE) + self.assertIn("NUMBER", TO_DB_OVERRIDE) + self.assertIn("TIMESTAMP", TO_DB_OVERRIDE) \ No newline at end of file diff --git a/tests/backends/test_connection_params.py b/tests/backends/test_connection_params.py index ef346760a..0685980ec 100644 --- a/tests/backends/test_connection_params.py +++ b/tests/backends/test_connection_params.py @@ -124,3 +124,42 @@ async def test_psycopg_connection_params(self): ) except ImportError: self.skipTest("psycopg not installed") + + async def test_dameng_connection_params(self): + with patch( + "tortoise.backends.dameng.client.dmPython.connect", new=AsyncMock() + ) as dm_connect: + await connections._init( + { + "models": { + "engine": "tortoise.backends.dameng", + "credentials": { + "database": "DAMENG", + "host": "127.0.0.1", + "password": "SYSDBA", + "port": 5236, + "user": "SYSDBA", + "charset": "utf8", + }, + } + }, + False, + ) + + # Mock the pool creation + with patch("tortoise.backends.dameng.pool.DmConnectionPool") as pool_mock: + pool_instance = AsyncMock() + pool_mock.return_value = pool_instance + + await connections.get("models").create_connection(with_db=True) + + pool_mock.assert_called_once_with( + host="127.0.0.1", + port=5236, + user="SYSDBA", + password="SYSDBA", + database="DAMENG", + charset="utf8", + minsize=1, + maxsize=5, + ) diff --git a/tests/backends/test_dameng.py b/tests/backends/test_dameng.py new file mode 100644 index 000000000..5fa90e659 --- /dev/null +++ b/tests/backends/test_dameng.py @@ -0,0 +1,359 @@ +""" +Test Dameng-specific features +""" + +from datetime import datetime, timedelta +from decimal import Decimal + +from tortoise import Tortoise, fields +from tortoise.contrib import test +from tortoise.contrib.dameng.fields import IntervalField, RowIDField, XMLField +from tortoise.contrib.dameng.functions import ( + Decode, + InitCap, + Instr, + ListAgg, + LPad, + NVL, + RPad, + RowNum, + SysDate, + ToChar, + ToDate, +) +from tortoise.models import Model + + +class DamengTestModel(Model): + id = fields.IntField(primary_key=True) + name = fields.CharField(max_length=50) + xml_data = XMLField(null=True) + interval_data = IntervalField(null=True) + row_id = RowIDField(null=True) + created_at = fields.DatetimeField(null=True) + status = fields.CharField(max_length=20, null=True) + score = fields.IntField(null=True) + category = fields.IntField(null=True) + + class Meta: + app = "models" + + +class TestDameng(test.SimpleTestCase): + async def asyncSetUp(self): + if Tortoise._inited: + await self._tearDownDB() + self.db_config = test.getDBConfig(app_label="models", modules=[__name__]) + if self.db_config["connections"]["models"]["engine"] != "tortoise.backends.dameng": + raise test.SkipTest("Dameng only") + await Tortoise.init(self.db_config, _create_db=True) + await Tortoise.generate_schemas() + + async def asyncTearDown(self) -> None: + await Tortoise._drop_databases() + await super().asyncTearDown() + + async def test_connection_params(self): + """Test Dameng connection parameters""" + conn = Tortoise.get_connection("models") + self.assertEqual(conn.client_class.__name__, "DmClient") + + async def test_xml_field(self): + """Test XMLField storage and retrieval""" + xml_content = 'Test' + obj = await DamengTestModel.create( + name="test_xml", + xml_data=xml_content + ) + + # Retrieve and verify + loaded = await DamengTestModel.get(id=obj.id) + self.assertEqual(loaded.xml_data, xml_content) + + async def test_interval_field(self): + """Test IntervalField storage and retrieval""" + interval = timedelta(hours=2, minutes=30, seconds=45) + obj = await DamengTestModel.create( + name="test_interval", + interval_data=interval + ) + + # Retrieve and verify + loaded = await DamengTestModel.get(id=obj.id) + self.assertEqual(loaded.interval_data, interval) + + async def test_tochar_function(self): + """Test ToChar function""" + now = datetime.now() + obj = await DamengTestModel.create( + name="test_tochar", + created_at=now + ) + + # Test date formatting + result = await DamengTestModel.filter(id=obj.id).annotate( + date_str=ToChar("created_at", "YYYY-MM-DD") + ).values("date_str") + + self.assertEqual(result[0]["date_str"], now.strftime("%Y-%m-%d")) + + async def test_todate_function(self): + """Test ToDate function""" + date_str = "2024-01-15" + + # Create test data + obj = await DamengTestModel.create( + name="test_todate", + created_at=datetime(2024, 1, 15) + ) + + # Test date parsing in filter + result = await DamengTestModel.filter( + created_at__gte=ToDate(date_str, "YYYY-MM-DD") + ).count() + + self.assertEqual(result, 1) + + async def test_sysdate_function(self): + """Test SysDate function""" + # Create past and future records + past = await DamengTestModel.create( + name="past", + created_at=datetime.now() - timedelta(days=1) + ) + future = await DamengTestModel.create( + name="future", + created_at=datetime.now() + timedelta(days=1) + ) + + # Filter using SysDate + result = await DamengTestModel.filter( + created_at__lte=SysDate() + ).count() + + self.assertEqual(result, 1) # Only past record + + async def test_initcap_function(self): + """Test InitCap function""" + await DamengTestModel.create(name="hello world") + + result = await DamengTestModel.annotate( + capitalized=InitCap("name") + ).values("capitalized") + + self.assertEqual(result[0]["capitalized"], "Hello World") + + async def test_instr_function(self): + """Test Instr function""" + await DamengTestModel.create(name="test@example.com") + + result = await DamengTestModel.annotate( + at_pos=Instr("name", "@") + ).values("at_pos") + + self.assertEqual(result[0]["at_pos"], 5) + + async def test_lpad_rpad_functions(self): + """Test LPad and RPad functions""" + await DamengTestModel.create(name="123") + + result = await DamengTestModel.annotate( + left_padded=LPad("name", 5, "0"), + right_padded=RPad("name", 5, "X") + ).values("left_padded", "right_padded") + + self.assertEqual(result[0]["left_padded"], "00123") + self.assertEqual(result[0]["right_padded"], "123XX") + + async def test_nvl_function(self): + """Test NVL function""" + # Create with NULL status + await DamengTestModel.create(name="test_nvl", status=None) + + result = await DamengTestModel.annotate( + status_with_default=NVL("status", "active") + ).values("status_with_default") + + self.assertEqual(result[0]["status_with_default"], "active") + + async def test_decode_function(self): + """Test Decode function""" + await DamengTestModel.create(name="test1", category=1) + await DamengTestModel.create(name="test2", category=2) + await DamengTestModel.create(name="test3", category=3) + + result = await DamengTestModel.annotate( + category_name=Decode( + "category", + 1, "Basic", + 2, "Premium", + 3, "Enterprise", + "Unknown" + ) + ).values("category_name") + + categories = [r["category_name"] for r in result] + self.assertIn("Basic", categories) + self.assertIn("Premium", categories) + self.assertIn("Enterprise", categories) + + async def test_rownum_function(self): + """Test RowNum function""" + # Create multiple records + for i in range(20): + await DamengTestModel.create(name=f"test_{i}") + + # Get first 10 using RowNum + result = await DamengTestModel.filter( + RowNum() <= 10 + ).count() + + self.assertEqual(result, 10) + + async def test_listagg_function(self): + """Test ListAgg function""" + # Create records in same category + for i in range(3): + await DamengTestModel.create( + name=f"item_{i}", + category=1 + ) + + result = await DamengTestModel.filter( + category=1 + ).annotate( + names=ListAgg("name", ",") + ).group_by("category").values("names") + + names = result[0]["names"].split(",") + self.assertEqual(len(names), 3) + self.assertIn("item_0", names) + + async def test_parameter_conversion(self): + """Test parameter placeholder conversion""" + # This tests the executor's parameter conversion + results = await DamengTestModel.filter( + name__in=["test1", "test2", "test3"] + ).all() + + # Should work without errors + self.assertIsInstance(results, list) + + async def test_connection_pool(self): + """Test connection pool functionality""" + conn = Tortoise.get_connection("models") + + # Test acquiring connections + async with conn._pool.acquire() as connection: + self.assertIsNotNone(connection) + + # Pool should still be healthy + self.assertTrue(hasattr(conn._pool, "_queue")) + + async def test_transaction_rollback(self): + """Test transaction rollback""" + conn = Tortoise.get_connection("models") + + try: + async with conn.in_transaction() as trans: + await DamengTestModel.create(name="will_rollback") + raise Exception("Force rollback") + except Exception: + pass + + # Record should not exist + count = await DamengTestModel.filter(name="will_rollback").count() + self.assertEqual(count, 0) + + async def test_bulk_operations(self): + """Test bulk insert and update""" + # Bulk create + objs = [ + DamengTestModel(name=f"bulk_{i}", score=i) + for i in range(100) + ] + await DamengTestModel.bulk_create(objs) + + # Verify + count = await DamengTestModel.filter(name__startswith="bulk_").count() + self.assertEqual(count, 100) + + # Bulk update + await DamengTestModel.filter( + name__startswith="bulk_" + ).update(status="updated") + + updated = await DamengTestModel.filter(status="updated").count() + self.assertEqual(updated, 100) + + async def test_decimal_precision(self): + """Test decimal field precision""" + # Create model with decimal field for testing + class DecimalModel(Model): + id = fields.IntField(primary_key=True) + amount = fields.DecimalField(max_digits=10, decimal_places=4) + + class Meta: + app = "models" + + await Tortoise.generate_schemas() + + # Test precise decimal + value = Decimal("1234.5678") + obj = await DecimalModel.create(amount=value) + loaded = await DecimalModel.get(id=obj.id) + + self.assertEqual(loaded.amount, value) + + async def test_unique_constraint(self): + """Test unique constraint enforcement""" + # Create model with unique field + class UniqueModel(Model): + id = fields.IntField(primary_key=True) + code = fields.CharField(max_length=20, unique=True) + + class Meta: + app = "models" + + await Tortoise.generate_schemas() + + # Create first record + await UniqueModel.create(code="UNIQUE001") + + # Try to create duplicate + with self.assertRaises(Exception): # IntegrityError + await UniqueModel.create(code="UNIQUE001") + + async def test_index_creation(self): + """Test index creation""" + # Model with indexes + class IndexedModel(Model): + id = fields.IntField(primary_key=True) + name = fields.CharField(max_length=50, index=True) + category = fields.CharField(max_length=20) + status = fields.CharField(max_length=20) + + class Meta: + app = "models" + indexes = [ + ["category", "status"], # Compound index + ] + + await Tortoise.generate_schemas() + + # Create and query using indexed fields + await IndexedModel.create( + name="indexed", + category="A", + status="active" + ) + + # These queries should use indexes + result = await IndexedModel.filter(name="indexed").exists() + self.assertTrue(result) + + result = await IndexedModel.filter( + category="A", + status="active" + ).exists() + self.assertTrue(result) \ No newline at end of file diff --git a/tests/backends/test_db_url.py b/tests/backends/test_db_url.py index df7dd9d5d..370ac9de9 100644 --- a/tests/backends/test_db_url.py +++ b/tests/backends/test_db_url.py @@ -419,3 +419,113 @@ def test_generate_config_many_apps(self): }, }, ) + + def test_dameng_basic(self): + res = expand_db_url("dm://SYSDBA:SYSDBA@127.0.0.1:5236/DAMENG") + self.assertEqual( + res, + { + "engine": "tortoise.backends.dameng", + "credentials": { + "database": "DAMENG", + "host": "127.0.0.1", + "password": "SYSDBA", + "port": 5236, + "user": "SYSDBA", + "charset": "utf8", + }, + }, + ) + + def test_dameng_encoded_password(self): + res = expand_db_url("dm://SYSDBA:kx%25jj5%2Fg@127.0.0.1:5236/DAMENG") + self.assertEqual( + res, + { + "engine": "tortoise.backends.dameng", + "credentials": { + "database": "DAMENG", + "host": "127.0.0.1", + "password": "kx%jj5/g", + "port": 5236, + "user": "SYSDBA", + "charset": "utf8", + }, + }, + ) + + def test_dameng_no_db(self): + res = expand_db_url("dm://SYSDBA:SYSDBA@127.0.0.1:5236") + self.assertEqual( + res, + { + "engine": "tortoise.backends.dameng", + "credentials": { + "database": None, + "host": "127.0.0.1", + "password": "SYSDBA", + "port": 5236, + "user": "SYSDBA", + "charset": "utf8", + }, + }, + ) + + def test_dameng_no_port(self): + res = expand_db_url("dm://SYSDBA:SYSDBA@127.0.0.1/DAMENG") + self.assertEqual( + res, + { + "engine": "tortoise.backends.dameng", + "credentials": { + "database": "DAMENG", + "host": "127.0.0.1", + "password": "SYSDBA", + "port": 5236, + "user": "SYSDBA", + "charset": "utf8", + }, + }, + ) + + def test_dameng_nonint_port(self): + with self.assertRaises(ConfigurationError): + expand_db_url("dm://SYSDBA:SYSDBA@127.0.0.1:moo/DAMENG") + + def test_dameng_testing(self): + res = expand_db_url(r"dm://SYSDBA:SYSDBA@127.0.0.1:5236/test_\{\}", testing=True) + database = res["credentials"]["database"] + self.assertIn("test_", database) + self.assertNotEqual("test_{}", database) + self.assertEqual( + res, + { + "engine": "tortoise.backends.dameng", + "credentials": { + "database": database, + "host": "127.0.0.1", + "password": "SYSDBA", + "port": 5236, + "user": "SYSDBA", + "charset": "utf8", + }, + }, + ) + + def test_dameng_params(self): + res = expand_db_url("dm://SYSDBA:SYSDBA@127.0.0.1:5236/DAMENG?charset=gbk&maxsize=10") + self.assertEqual( + res, + { + "engine": "tortoise.backends.dameng", + "credentials": { + "database": "DAMENG", + "host": "127.0.0.1", + "password": "SYSDBA", + "port": 5236, + "user": "SYSDBA", + "charset": "gbk", + "maxsize": "10", + }, + }, + ) diff --git a/tests/contrib/test_dameng.py b/tests/contrib/test_dameng.py new file mode 100644 index 000000000..e8ae1eabf --- /dev/null +++ b/tests/contrib/test_dameng.py @@ -0,0 +1,179 @@ +""" +Test Dameng contrib module +""" + +import unittest +from datetime import datetime, timedelta +from decimal import Decimal + +from tortoise.contrib.dameng.fields import IntervalField, RowIDField, XMLField +from tortoise.contrib.dameng.functions import ( + Decode, + InitCap, + Instr, + ListAgg, + LPad, + NVL, + RPad, + RowNum, + SysDate, + ToChar, + ToDate, +) +from tortoise.expressions import F, Q + + +class TestDamengFields(unittest.TestCase): + """Test Dameng-specific field definitions""" + + def test_xml_field_init(self): + """Test XMLField initialization""" + field = XMLField() + self.assertEqual(field.field_type, str) + self.assertEqual(field.SQL_TYPE, "XMLTYPE") + + def test_interval_field_init(self): + """Test IntervalField initialization""" + field = IntervalField() + self.assertEqual(field.field_type, str) + self.assertEqual(field.interval_type, "DAY TO SECOND") + + def test_rowid_field_init(self): + """Test RowIDField initialization""" + field = RowIDField() + self.assertEqual(field.field_type, str) + self.assertEqual(field.SQL_TYPE, "ROWID") + self.assertTrue(field.generated) + + def test_xml_field_to_db_value(self): + """Test XMLField to_db_value""" + field = XMLField() + xml_str = 'test' + self.assertEqual(field.to_db_value(xml_str, None), xml_str) + self.assertIsNone(field.to_db_value(None, None)) + + def test_interval_field_to_db_value(self): + """Test IntervalField to_db_value""" + field = IntervalField() + interval = "1 02:30:00" # 1 day, 2 hours, 30 minutes + # Should return string as is + self.assertEqual(field.to_db_value(interval, None), "1 02:30:00") + self.assertIsNone(field.to_db_value(None, None)) + + def test_interval_field_to_python_value(self): + """Test IntervalField to_python_value""" + field = IntervalField() + # Test with interval string + interval_str = "1 02:03:00" # 1 day, 2 hours, 3 minutes + result = field.to_python_value(interval_str) + self.assertEqual(result, "1 02:03:00") + + # Test with None + self.assertIsNone(field.to_python_value(None)) + + +class TestDamengFunctions(unittest.TestCase): + """Test Dameng-specific SQL functions""" + + def test_tochar_function(self): + """Test ToChar function creation""" + func = ToChar("created_at", "YYYY-MM-DD") + self.assertEqual(func.name, "TO_CHAR") + + # Test with field reference + func = ToChar(F("created_at"), "DD/MM/YYYY") + self.assertEqual(func.name, "TO_CHAR") + + def test_todate_function(self): + """Test ToDate function creation""" + func = ToDate("2024-01-01", "YYYY-MM-DD") + self.assertEqual(func.name, "TO_DATE") + + def test_sysdate_function(self): + """Test SysDate function creation""" + func = SysDate() + self.assertEqual(func.name, "SYSDATE") + + def test_initcap_function(self): + """Test InitCap function creation""" + func = InitCap("name") + self.assertEqual(func.name, "INITCAP") + + # Test with field reference + func = InitCap(F("title")) + self.assertEqual(func.name, "INITCAP") + + def test_instr_function(self): + """Test Instr function creation""" + func = Instr("email", "@") + self.assertEqual(func.name, "INSTR") + + # Test with optional parameters + func = Instr("text", "search", 1, 1) + self.assertEqual(func.name, "INSTR") + + def test_lpad_function(self): + """Test LPad function creation""" + func = LPad("id", 5, "0") + self.assertEqual(func.name, "LPAD") + + # Test with field reference + func = LPad(F("code"), 10, "X") + self.assertEqual(func.name, "LPAD") + + def test_rpad_function(self): + """Test RPad function creation""" + func = RPad("name", 20, " ") + self.assertEqual(func.name, "RPAD") + + def test_nvl_function(self): + """Test NVL function creation""" + func = NVL("status", "active") + self.assertEqual(func.name, "NVL") + + # Test with field references + func = NVL(F("status"), F("default_status")) + self.assertEqual(func.name, "NVL") + + def test_decode_function(self): + """Test Decode function creation""" + func = Decode("type", 1, "A", 2, "B", "C") + self.assertEqual(func.name, "DECODE") + + # Test with multiple pairs + func = Decode(F("category"), "1", "Basic", "2", "Premium", "Unknown") + self.assertEqual(func.name, "DECODE") + + def test_rownum_function(self): + """Test RowNum function creation""" + func = RowNum() + self.assertEqual(func.name, "ROWNUM") + + def test_listagg_function(self): + """Test ListAgg function creation""" + func = ListAgg("name", ",") + self.assertEqual(func.name, "LISTAGG") + + # Test with order by + func = ListAgg(F("name"), " | ", F("created_at")) + self.assertEqual(func.name, "LISTAGG") + + def test_function_combinations(self): + """Test combining multiple functions""" + # NVL with ToChar + func = NVL(ToChar("date", "YYYY-MM-DD"), "N/A") + self.assertEqual(func.name, "NVL") + + # InitCap with NVL + func = InitCap(NVL("name", "unnamed")) + self.assertEqual(func.name, "INITCAP") + + def test_function_in_expressions(self): + """Test functions in query expressions""" + # In Q objects + q = Q(status=NVL(F("status"), "active")) + self.assertIsInstance(q, Q) + + # Functions can be used in expressions + func = RowNum() + self.assertEqual(func.name, "ROWNUM") \ No newline at end of file diff --git a/tests/contrib/test_sanic.py b/tests/contrib/test_sanic.py new file mode 100644 index 000000000..9ac37cab8 --- /dev/null +++ b/tests/contrib/test_sanic.py @@ -0,0 +1,243 @@ +""" +Test Sanic contrib integration +""" + +import unittest +from unittest.mock import AsyncMock, MagicMock, patch + +from tortoise.contrib.sanic import register_tortoise + + +class TestSanicIntegration(unittest.TestCase): + """Test Sanic Tortoise integration""" + + def setUp(self): + """Set up test fixtures""" + self.app = MagicMock() + self.app.ctx = MagicMock() + self.app.before_server_start = MagicMock() + self.app.after_server_stop = MagicMock() + self.app.middleware = MagicMock() + + def test_register_tortoise_basic(self): + """Test basic registration""" + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"models": ["tests.testmodels"]}, + ) + + # Check that listeners were registered + self.app.before_server_start.assert_called_once() + self.app.after_server_stop.assert_called_once() + + def test_register_tortoise_with_config(self): + """Test registration with config dict""" + config = { + "connections": { + "default": { + "engine": "tortoise.backends.sqlite", + "credentials": {"file_path": ":memory:"}, + } + }, + "apps": { + "models": { + "models": ["tests.testmodels"], + "default_connection": "default", + } + }, + } + + register_tortoise(self.app, config=config) + + # Check that listeners were registered + self.app.before_server_start.assert_called_once() + self.app.after_server_stop.assert_called_once() + + def test_register_tortoise_config_file(self): + """Test registration with config file""" + with patch("tortoise.contrib.sanic.generate_config_from_file") as mock_gen: + mock_gen.return_value = { + "connections": {"default": {}}, + "apps": {"models": {}}, + } + + register_tortoise(self.app, config_file="config.json") + + mock_gen.assert_called_once_with("config.json") + self.app.before_server_start.assert_called_once() + self.app.after_server_stop.assert_called_once() + + def test_register_tortoise_with_generate_schemas(self): + """Test registration with generate_schemas=True""" + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"models": ["tests.testmodels"]}, + generate_schemas=True, + ) + + # Verify the before_server_start handler was registered + self.app.before_server_start.assert_called_once() + + def test_register_tortoise_session_management(self): + """Test session management middleware registration""" + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"models": ["tests.testmodels"]}, + session_management=True, + ) + + # Check that middleware was registered + self.app.middleware.assert_called_once_with("request") + + @patch("tortoise.contrib.sanic.Tortoise") + async def test_init_orm_handler(self, mock_tortoise): + """Test the init_orm handler""" + mock_tortoise.init = AsyncMock() + + # Register and get the handler + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"models": ["tests.testmodels"]}, + ) + + # Get the registered handler + handler = self.app.before_server_start.call_args[0][0] + + # Call the handler + await handler(self.app) + + # Verify Tortoise.init was called + mock_tortoise.init.assert_called_once() + + @patch("tortoise.contrib.sanic.Tortoise") + async def test_close_orm_handler(self, mock_tortoise): + """Test the close_orm handler""" + mock_tortoise.close_connections = AsyncMock() + + # Register and get the handler + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"models": ["tests.testmodels"]}, + ) + + # Get the registered handler + handler = self.app.after_server_stop.call_args[0][0] + + # Call the handler + await handler(self.app) + + # Verify Tortoise.close_connections was called + mock_tortoise.close_connections.assert_called_once() + + @patch("tortoise.contrib.sanic.connections") + async def test_session_middleware(self, mock_connections): + """Test session management middleware""" + # Mock connection + mock_conn = AsyncMock() + mock_conn.in_transaction = MagicMock() + mock_connections.get.return_value = mock_conn + + # Mock transaction context + mock_trans = AsyncMock() + mock_trans.__aenter__ = AsyncMock(return_value=mock_trans) + mock_trans.__aexit__ = AsyncMock(return_value=None) + mock_conn.in_transaction.return_value = mock_trans + + # Register with session management + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"models": ["tests.testmodels"]}, + session_management=True, + ) + + # Get the middleware + middleware_decorator = self.app.middleware.call_args[0][0] + middleware_func = self.app.middleware.return_value.call_args[0][0] + + # Create mock request + request = MagicMock() + request.ctx = MagicMock() + + # Call middleware + await middleware_func(request) + + # Verify transaction was created + mock_conn.in_transaction.assert_called_once() + mock_trans.__aenter__.assert_called_once() + + def test_invalid_config_params(self): + """Test that invalid config parameters raise errors""" + with self.assertRaises(ValueError): + register_tortoise(self.app) # No config provided + + with self.assertRaises(ValueError): + register_tortoise( + self.app, + db_url="sqlite://:memory:", + config={"connections": {}}, # Both db_url and config + ) + + def test_app_context_storage(self): + """Test that config is stored in app context""" + config = { + "connections": {"default": {}}, + "apps": {"models": {}}, + } + + register_tortoise(self.app, config=config) + + # Verify config was stored in app context + self.assertEqual(self.app.ctx.tortoise_config, config) + + async def test_generate_schemas_handler(self): + """Test generate_schemas in init handler""" + with patch("tortoise.contrib.sanic.Tortoise") as mock_tortoise: + mock_tortoise.init = AsyncMock() + mock_tortoise.generate_schemas = AsyncMock() + + # Register with generate_schemas=True + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"models": ["tests.testmodels"]}, + generate_schemas=True, + ) + + # Get and call the handler + handler = self.app.before_server_start.call_args[0][0] + await handler(self.app) + + # Verify generate_schemas was called + mock_tortoise.generate_schemas.assert_called_once() + + def test_custom_app_name(self): + """Test custom app name in modules""" + register_tortoise( + self.app, + db_url="sqlite://:memory:", + modules={"myapp": ["myapp.models"]}, + ) + + # Check config was created with custom app name + expected_config = { + "connections": { + "default": { + "engine": "tortoise.backends.sqlite", + "credentials": {"file_path": ":memory:"}, + } + }, + "apps": { + "myapp": { + "models": ["myapp.models"], + "default_connection": "default", + } + }, + } + + self.assertEqual(self.app.ctx.tortoise_config, expected_config) \ No newline at end of file diff --git a/tests/test_config_dameng.py b/tests/test_config_dameng.py new file mode 100644 index 000000000..873b0d399 --- /dev/null +++ b/tests/test_config_dameng.py @@ -0,0 +1,35 @@ +""" +Test configuration for Dameng database +""" + +import os + +# Dameng test database configuration +DAMENG_TEST_DB = { + "connections": { + "default": { + "engine": "tortoise.backends.dameng", + "credentials": { + "host": os.getenv("DAMENG_HOST", "127.0.0.1"), + "port": int(os.getenv("DAMENG_PORT", "5236")), + "user": os.getenv("DAMENG_USER", "SYSDBA"), + "password": os.getenv("DAMENG_PASSWORD", "SYSDBA"), + "database": os.getenv("DAMENG_DATABASE", "DAMENG"), + "minsize": 1, + "maxsize": 5, + "charset": "utf8", + } + } + }, + "apps": { + "models": { + "models": ["tests.testmodels"], + "default_connection": "default", + } + }, + "use_tz": False, + "timezone": "UTC", +} + +# Test database URL +DAMENG_TEST_URL = "dm://SYSDBA:SYSDBA@127.0.0.1:5236/DAMENG" \ No newline at end of file diff --git a/tortoise/backends/base/config_generator.py b/tortoise/backends/base/config_generator.py index 260f7b422..820aab2d7 100644 --- a/tortoise/backends/base/config_generator.py +++ b/tortoise/backends/base/config_generator.py @@ -15,6 +15,7 @@ urlparse.uses_netloc.append("mysql") urlparse.uses_netloc.append("oracle") urlparse.uses_netloc.append("mssql") +urlparse.uses_netloc.append("dm") DB_LOOKUP: dict[str, dict[str, Any]] = { "psycopg": { "engine": "tortoise.backends.psycopg", @@ -124,6 +125,24 @@ "pool_recycle": int, }, }, + "dm": { + "engine": "tortoise.backends.dameng", + "vmap": { + "path": "database", + "hostname": "host", + "port": "port", + "username": "user", + "password": "password", + }, + "defaults": {"port": 5236, "charset": "utf8"}, + "cast": { + "pool_min_size": int, + "pool_max_size": int, + "pool_max_idle_time": int, + "pool_acquire_timeout": int, + "connect_timeout": int, + }, + }, } # Create an alias for backwards compatibility DB_LOOKUP["postgres"] = DB_LOOKUP["asyncpg"] diff --git a/tortoise/backends/dameng/__init__.py b/tortoise/backends/dameng/__init__.py new file mode 100644 index 000000000..1139eac06 --- /dev/null +++ b/tortoise/backends/dameng/__init__.py @@ -0,0 +1,3 @@ +from .client import DmClient + +client_class = DmClient \ No newline at end of file diff --git a/tortoise/backends/dameng/client.py b/tortoise/backends/dameng/client.py new file mode 100644 index 000000000..485279ca0 --- /dev/null +++ b/tortoise/backends/dameng/client.py @@ -0,0 +1,571 @@ +from __future__ import annotations + +import asyncio +import re +from concurrent.futures import ThreadPoolExecutor +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Coroutine, + Dict, + List, + Optional, + Tuple, + TypeVar, + Union, +) + +import dmPython +from pypika import Query + +from tortoise.backends.base.client import ( + BaseDBAsyncClient, + Capabilities, + ConnectionWrapper, + NestedTransactionContext, + PoolConnectionWrapper, + TransactionContext, + TransactionContextPooled, +) +from tortoise.exceptions import ( + DBConnectionError, + DoesNotExist, + IntegrityError, + OperationalError, + TransactionManagementError, +) +from tortoise.log import db_client_logger + +from .executor import DmExecutor +from .pool import DmConnectionPool +from .schema_generator import DmSchemaGenerator + +if TYPE_CHECKING: + from tortoise.models import Model + +FuncType = Callable[..., Coroutine[Any, Any, Any]] +T = TypeVar("T") + +logger = db_client_logger + + +class DmClient(BaseDBAsyncClient): + """Dameng database async client""" + + executor_class = DmExecutor + schema_generator = DmSchemaGenerator + query_class = Query + + capabilities = Capabilities( + dialect="dm", + daemon=False, + requires_limit=True, + inline_comment=True, + supports_transactions=True, + support_for_update=True, + support_index_hint=False, + support_update_limit_order_by=False, + ) + + def __init__(self, connection_name: str, **kwargs: Any) -> None: + """Initialize Dameng database client with connection pooling support""" + super().__init__(connection_name=connection_name, **kwargs) + self._connection = None + self._connection_params = kwargs + self._pool: Optional[DmConnectionPool] = None + self._thread_pool = ThreadPoolExecutor(max_workers=10) + self._transaction_context: Dict[int, Dict[str, Any]] = {} + + # Connection pool configuration + self._pool_config = { + 'min_size': kwargs.get('pool_min_size', 1), + 'max_size': kwargs.get('pool_max_size', 10), + 'max_idle_time': kwargs.get('pool_max_idle_time', 300), # 5 minutes + 'acquire_timeout': kwargs.get('pool_acquire_timeout', 30), # 30 seconds + } + + async def create_connection(self, with_db: bool = True) -> None: + """Create database connection with pooling support""" + if self._pool: + return + + params = self._connection_params.copy() + + # Build connection parameters + connection_config = { + 'host': params.get("host", "localhost"), + 'port': params.get("port", 5236), + 'user': params.get("user", "SYSDBA"), + 'password': params.get("password", "SYSDBA"), + 'database': params.get("database", "SYSDBA"), + 'charset': params.get("charset", "utf8"), + 'connect_timeout': params.get("connect_timeout", 10), + } + + try: + # Create connection pool + self._pool = DmConnectionPool( + connection_config=connection_config, + min_size=self._pool_config['min_size'], + max_size=self._pool_config['max_size'], + max_idle_time=self._pool_config['max_idle_time'], + acquire_timeout=self._pool_config['acquire_timeout'], + executor=self._thread_pool + ) + + # Initialize connection pool + await self._pool.initialize() + + logger.info( + f"Dameng database connection pool created - " + f"{connection_config['host']}:{connection_config['port']}/{connection_config['database']}" + ) + + except Exception as e: + error_msg = ( + f"Unable to connect to Dameng database " + f"{connection_config['host']}:{connection_config['port']}: {e}" + ) + logger.error(error_msg) + raise DBConnectionError(error_msg) + + async def close(self) -> None: + """Close database connection pool and all resources""" + try: + if self._pool: + await self._pool.close() + self._pool = None + + if self._thread_pool: + self._thread_pool.shutdown(wait=True) + self._thread_pool = None + + logger.info("Dameng database connection closed") + except Exception as e: + logger.error(f"Error closing Dameng database connection: {e}") + + async def db_create(self) -> None: + """Create database - Usually pre-created by DBA for Dameng""" + logger.info(f"Database {self.database} initialization check") + + try: + # Check database connection + await self.execute_query("SELECT 1") + logger.info("Database connection is normal") + except Exception as e: + logger.error(f"Database connection check failed: {e}") + raise + + async def db_delete(self) -> None: + """Delete database - Not supported through application for Dameng""" + logger.warning("Dameng database does not support programmatic database deletion") + raise NotImplementedError("Dameng database does not support programmatic database deletion") + + def acquire_connection(self) -> "DmClient": + """Acquire connection - Returns current client instance for compatibility""" + return self + + async def execute_query( + self, query: str, values: Optional[List[Any]] = None + ) -> Tuple[int, List[Dict[str, Any]]]: + """Execute query with connection pooling and error handling""" + if not self._pool: + await self.create_connection() + + # Convert parameters using executor method + converted_query = self._convert_query_parameters(query) if values else query + + connection = None + cursor = None + + try: + # Get connection from pool + connection = await self._pool.acquire() + cursor = await asyncio.get_event_loop().run_in_executor(None, connection.cursor) + + # Execute query + if values: + await asyncio.get_event_loop().run_in_executor( + None, cursor.execute, converted_query, values + ) + else: + await asyncio.get_event_loop().run_in_executor( + None, cursor.execute, converted_query + ) + + # Handle results + if query.strip().upper().startswith(("SELECT", "WITH", "SHOW", "DESCRIBE", "EXPLAIN")): + # Query operations + rows = await asyncio.get_event_loop().run_in_executor(None, cursor.fetchall) + columns = [desc[0] for desc in cursor.description] if cursor.description else [] + result = [dict(zip(columns, row)) for row in rows] + return len(result), result + else: + # Modification operations (INSERT, UPDATE, DELETE) + rowcount = cursor.rowcount + # Commit if not in transaction + if not self.in_transaction(): + await asyncio.get_event_loop().run_in_executor(None, connection.commit) + return rowcount, [] + + except dmPython.Error as e: + # dmPython specific error handling + if connection and not self.in_transaction(): + await asyncio.get_event_loop().run_in_executor(None, connection.rollback) + + error_msg = str(e) + error_code = getattr(e, 'code', 0) if hasattr(e, 'code') else 0 + + # Categorize errors + if "duplicate" in error_msg.lower() or error_code in [1, 2601, 2627]: + raise IntegrityError(f"Unique constraint violation: {error_msg}") + elif "foreign key" in error_msg.lower() or error_code in [547]: + raise IntegrityError(f"Foreign key constraint violation: {error_msg}") + elif "connection" in error_msg.lower() or error_code in [40001, 40197]: + raise DBConnectionError(f"Database connection error: {error_msg}") + elif "not found" in error_msg.lower(): + raise DoesNotExist(f"Record not found: {error_msg}") + else: + raise OperationalError(f"Database operation failed: {error_msg}") + + except Exception as e: + # General error handling + if connection and not self.in_transaction(): + try: + await asyncio.get_event_loop().run_in_executor(None, connection.rollback) + except Exception: + pass + + logger.error(f"Query execution failed - SQL: {query[:100]}..., Error: {e}") + raise OperationalError(f"Query execution failed: {e}") + + finally: + # Cleanup resources + if cursor: + try: + await asyncio.get_event_loop().run_in_executor(None, cursor.close) + except Exception: + pass + if connection: + await self._pool.release(connection) + + async def execute_insert(self, query: str, values: List[Any]) -> int: + """Execute insert operation - Returns affected row count""" + try: + rowcount, _ = await self.execute_query(query, values) + logger.debug(f"Insert operation completed, affected {rowcount} rows") + return rowcount + except Exception as e: + logger.error(f"Insert operation failed - SQL: {query[:100]}..., Error: {e}") + raise + + async def execute_many(self, query: str, values: List[List[Any]]) -> None: + """Batch execute with transaction support and error recovery""" + if not self._pool: + await self.create_connection() + + if not values: + return + + # Convert parameters + converted_query = self._convert_query_parameters(query) + + connection = None + cursor = None + + try: + connection = await self._pool.acquire() + cursor = await asyncio.get_event_loop().run_in_executor(None, connection.cursor) + + # Execute batch operation + await asyncio.get_event_loop().run_in_executor( + None, cursor.executemany, converted_query, values + ) + + # Commit if not in transaction + if not self.in_transaction(): + await asyncio.get_event_loop().run_in_executor(None, connection.commit) + + logger.debug(f"Batch execution successful, affected {len(values)} records") + + except Exception as e: + if connection and not self.in_transaction(): + try: + await asyncio.get_event_loop().run_in_executor(None, connection.rollback) + except Exception: + pass + + logger.error( + f"Batch execution failed - SQL: {query[:100]}..., " + f"Records: {len(values)}, Error: {e}" + ) + raise OperationalError(f"Batch execution failed: {e}") + + finally: + if cursor: + try: + await asyncio.get_event_loop().run_in_executor(None, cursor.close) + except Exception: + pass + if connection: + await self._pool.release(connection) + + async def execute_script(self, query: str) -> None: + """Execute SQL script with multi-statement support and transaction handling""" + if not query.strip(): + return + + # Split multiple SQL statements (considering semicolons in strings) + statements = self._split_sql_statements(query) + + if not statements: + return + + # Debug: print first few statements + for i, stmt in enumerate(statements[:5]): + logger.debug(f"Script statement {i+1}: {stmt[:200]}...") + + # Use transaction for multiple statements + should_use_transaction = len(statements) > 1 and not self.in_transaction() + + try: + if should_use_transaction: + await self.start_transaction() + + for i, statement in enumerate(statements): + try: + await self.execute_query(statement) + logger.debug(f"Script statement {i+1}/{len(statements)} executed successfully") + except Exception as e: + logger.error(f"Script statement {i+1} failed: {statement[:50]}..., Error: {e}") + raise + + if should_use_transaction: + await self.commit_transaction() + + logger.info(f"SQL script execution completed, {len(statements)} statements") + + except Exception as e: + if should_use_transaction: + await self.rollback_transaction() + logger.error(f"SQL script execution failed: {e}") + raise + + def _split_sql_statements(self, script: str) -> List[str]: + """Intelligently split SQL statements, avoiding semicolons in strings""" + statements = [] + current_statement = "" + in_single_quote = False + in_double_quote = False + + i = 0 + while i < len(script): + char = script[i] + + if char == "'" and not in_double_quote: + in_single_quote = not in_single_quote + elif char == '"' and not in_single_quote: + in_double_quote = not in_double_quote + elif char == ';' and not in_single_quote and not in_double_quote: + # Statement ends + statement = current_statement.strip() + if statement: + statements.append(statement) + current_statement = "" + i += 1 + continue + + current_statement += char + i += 1 + + # Add last statement + statement = current_statement.strip() + if statement: + statements.append(statement) + + return statements + + async def start_transaction(self) -> None: + """Start transaction with context management support""" + if not self._pool: + await self.create_connection() + + task_id = id(asyncio.current_task()) + + if task_id in self._transaction_context: + # Nested transaction, increase level + self._transaction_context[task_id]['level'] += 1 + logger.debug( + f"Nested transaction started, level: {self._transaction_context[task_id]['level']}" + ) + return + + try: + # Get dedicated connection for transaction + connection = await self._pool.acquire() + # dmPython doesn't support autocommit attribute, transactions managed via commit/rollback + + self._transaction_context[task_id] = { + 'connection': connection, + 'level': 1, + 'committed': False + } + + logger.debug(f"Transaction started - Task ID: {task_id}") + + except Exception as e: + logger.error(f"Failed to start transaction: {e}") + raise OperationalError(f"Failed to start transaction: {e}") + + async def commit_transaction(self) -> None: + """Commit transaction with nested transaction support""" + task_id = id(asyncio.current_task()) + + if task_id not in self._transaction_context: + logger.warning("Attempting to commit non-existent transaction") + return + + tx_context = self._transaction_context[task_id] + + if tx_context['level'] > 1: + # Nested transaction, decrease level + tx_context['level'] -= 1 + logger.debug(f"Nested transaction level decreased to: {tx_context['level']}") + return + + try: + connection = tx_context['connection'] + await asyncio.get_event_loop().run_in_executor(None, connection.commit) + tx_context['committed'] = True + + logger.debug(f"Transaction committed successfully - Task ID: {task_id}") + + except Exception as e: + logger.error(f"Transaction commit failed: {e}") + raise OperationalError(f"Transaction commit failed: {e}") + + finally: + # Cleanup transaction context + await self._cleanup_transaction(task_id) + + async def rollback_transaction(self) -> None: + """Rollback transaction with nested transaction support""" + task_id = id(asyncio.current_task()) + + if task_id not in self._transaction_context: + logger.warning("Attempting to rollback non-existent transaction") + return + + tx_context = self._transaction_context[task_id] + + try: + connection = tx_context['connection'] + await asyncio.get_event_loop().run_in_executor(None, connection.rollback) + + logger.debug(f"Transaction rolled back successfully - Task ID: {task_id}") + + except Exception as e: + logger.error(f"Transaction rollback failed: {e}") + raise OperationalError(f"Transaction rollback failed: {e}") + + finally: + # Cleanup transaction context (rollback clears entire transaction) + await self._cleanup_transaction(task_id) + + async def _cleanup_transaction(self, task_id: int) -> None: + """Cleanup transaction context and release connection""" + if task_id in self._transaction_context: + tx_context = self._transaction_context[task_id] + connection = tx_context['connection'] + + # Release connection back to pool + await self._pool.release(connection) + + # Delete transaction context + del self._transaction_context[task_id] + + def in_transaction(self) -> bool: + """Check if current task is in a transaction""" + task_id = id(asyncio.current_task()) + return task_id in self._transaction_context + + def _convert_query_parameters(self, query: str) -> str: + """Convert parameter placeholders from :1, :2 format to ? format""" + # Save all string contents to avoid replacing parameters in strings + strings = [] + string_pattern = re.compile(r"'[^']*'") + + # Temporarily replace strings with placeholders + def save_string(match): + strings.append(match.group(0)) + return f"__STR_{len(strings)-1}__" + + query_with_placeholders = string_pattern.sub(save_string, query) + + # Replace parameters + query_with_placeholders = DmExecutor.PARAM_PATTERN.sub('?', query_with_placeholders) + + # Restore strings + for i, string in enumerate(strings): + query_with_placeholders = query_with_placeholders.replace(f"__STR_{i}__", string) + + return query_with_placeholders + + @property + def database(self) -> str: + """Get database name""" + return self._connection_params.get("database", "SYSDBA") + + @property + def connection_info(self) -> Dict[str, Any]: + """Get connection info (excluding sensitive data)""" + return { + "host": self._connection_params.get("host", "localhost"), + "port": self._connection_params.get("port", 5236), + "database": self._connection_params.get("database", "SYSDBA"), + "user": self._connection_params.get("user", "SYSDBA"), + "charset": self._connection_params.get("charset", "utf8"), + "pool_config": self._pool_config + } + + async def health_check(self) -> Dict[str, Any]: + """Health check - Test database connection status""" + try: + if not self._pool: + return { + "status": "unhealthy", + "error": "Connection pool not initialized" + } + + # Execute simple query to test connection + _, result = await self.execute_query("SELECT 1 as test") + + pool_status = await self._pool.get_status() + + return { + "status": "healthy", + "database": self.database, + "pool_status": pool_status, + "test_query": "Success" if result and result[0].get("test") == 1 else "Failed" + } + + except Exception as e: + logger.error(f"Database health check failed: {e}") + return { + "status": "unhealthy", + "error": str(e) + } + + def __del__(self): + """Destructor - Ensure resource cleanup""" + try: + if self._pool: + # Close asynchronously in event loop + loop = asyncio.get_event_loop() + if loop.is_running(): + loop.create_task(self.close()) + else: + loop.run_until_complete(self.close()) + except Exception: + pass \ No newline at end of file diff --git a/tortoise/backends/dameng/executor.py b/tortoise/backends/dameng/executor.py new file mode 100644 index 000000000..9f9168607 --- /dev/null +++ b/tortoise/backends/dameng/executor.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import re +from typing import Any, List + +from tortoise.backends.base.executor import BaseExecutor + +from .types import quote_identifier + + +class DmExecutor(BaseExecutor): + """Dameng database executor - Handles SQL execution and parameter processing""" + + EXPLAIN_PREFIX = "EXPLAIN PLAN FOR" + + # Parameter placeholder regex - For precise matching and replacement + PARAM_PATTERN = re.compile(r':(\d+)\b') + + def parameter(self, pos: int) -> str: + """Return parameter placeholder - Dameng uses :number format""" + return f":{pos}" + + def quote(self, name: str) -> str: + """Quote identifier - Dameng uses double quotes, handles keywords automatically""" + return quote_identifier(name) + + def convert_parameters(self, query: str) -> str: + """Convert :1, :2 format parameter placeholders to dmPython's ? format""" + # Save all string contents to avoid replacing parameters inside strings + strings = [] + string_pattern = re.compile(r"'[^']*'") + + # Temporarily replace strings with placeholders + def save_string(match): + strings.append(match.group(0)) + return f"__STR_{len(strings)-1}__" + + query_with_placeholders = string_pattern.sub(save_string, query) + + # Replace parameters + query_with_placeholders = self.PARAM_PATTERN.sub('?', query_with_placeholders) + + # Restore strings + for i, string in enumerate(strings): + query_with_placeholders = query_with_placeholders.replace( + f"__STR_{i}__", string + ) + + return query_with_placeholders \ No newline at end of file diff --git a/tortoise/backends/dameng/pool.py b/tortoise/backends/dameng/pool.py new file mode 100644 index 000000000..216fd2a1e --- /dev/null +++ b/tortoise/backends/dameng/pool.py @@ -0,0 +1,453 @@ +"""Dameng database connection pool implementation. + +Supports connection management, health checks, and fault recovery. +""" + +from __future__ import annotations + +import asyncio +import time +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + +import dmPython + +from tortoise.log import db_client_logger + +logger = db_client_logger + + +@dataclass +class ConnectionInfo: + """Connection information - Tracks connection state and usage""" + + connection: Any + created_at: float = field(default_factory=time.time) + last_used_at: float = field(default_factory=time.time) + in_use: bool = False + is_healthy: bool = True + use_count: int = 0 + + def mark_used(self) -> None: + """Mark connection as used""" + self.last_used_at = time.time() + self.use_count += 1 + + def is_idle_too_long(self, max_idle_time: int) -> bool: + """Check if connection has been idle too long""" + return time.time() - self.last_used_at > max_idle_time + + def is_expired(self, max_lifetime: int = 3600) -> bool: + """Check if connection is expired (default 1 hour)""" + return time.time() - self.created_at > max_lifetime + + +class DmConnectionPool: + """Dameng database connection pool - Enterprise-grade connection management""" + + def __init__( + self, + connection_config: Dict[str, Any], + min_size: int = 1, + max_size: int = 10, + max_idle_time: int = 300, # 5 minutes + acquire_timeout: int = 30, # 30 seconds + health_check_interval: int = 60, # 1 minute + max_lifetime: int = 3600, # 1 hour + executor: Optional[ThreadPoolExecutor] = None + ) -> None: + """Initialize connection pool. + + Args: + connection_config: Database connection configuration + min_size: Minimum number of connections + max_size: Maximum number of connections + max_idle_time: Maximum idle time in seconds + acquire_timeout: Timeout for acquiring connection in seconds + health_check_interval: Health check interval in seconds + max_lifetime: Maximum connection lifetime in seconds + executor: Thread pool executor + """ + self._config = connection_config.copy() + self._min_size = max(1, min_size) + self._max_size = max(min_size, max_size) + self._max_idle_time = max_idle_time + self._acquire_timeout = acquire_timeout + self._health_check_interval = health_check_interval + self._max_lifetime = max_lifetime + + # Connection pool state + self._connections: List[ConnectionInfo] = [] + self._lock = asyncio.Lock() + self._closed = False + self._initialized = False + + # Statistics + self._stats = { + 'total_connections': 0, + 'active_connections': 0, + 'created_connections': 0, + 'closed_connections': 0, + 'failed_connections': 0, + 'acquire_timeouts': 0, + 'health_check_failures': 0, + } + + # Thread pool for executing synchronous database operations + self._executor = executor or ThreadPoolExecutor(max_workers=max_size * 2) + self._own_executor = executor is None + + # Health check task + self._health_check_task: Optional[asyncio.Task] = None + + logger.info( + f"Connection pool initialized - min: {self._min_size}, max: {self._max_size}, " + f"database: {self._config['database']}" + ) + + async def initialize(self) -> None: + """Initialize connection pool - Create minimum connections""" + if self._initialized: + return + + async with self._lock: + if self._initialized: + return + + try: + # Create minimum connections + for i in range(self._min_size): + try: + conn_info = await self._create_connection() + self._connections.append(conn_info) + self._stats['created_connections'] += 1 + logger.debug(f"Created initial connection {i+1}/{self._min_size}") + except Exception as e: + logger.error(f"Failed to create initial connection: {e}") + if i == 0: # If first connection fails, raise exception + raise + + # Start health check task + self._health_check_task = asyncio.create_task(self._health_check_loop()) + + self._initialized = True + self._stats['total_connections'] = len(self._connections) + + logger.info(f"Connection pool initialized - current connections: {len(self._connections)}") + + except Exception as e: + logger.error(f"Connection pool initialization failed: {e}") + await self.close() + raise + + async def _create_connection(self) -> ConnectionInfo: + """Create new database connection""" + try: + # Create synchronous connection in thread pool + connection = await asyncio.get_event_loop().run_in_executor( + self._executor, self._create_sync_connection + ) + + conn_info = ConnectionInfo(connection=connection) + logger.debug(f"Successfully created connection to {self._config['host']}:{self._config['port']}") + + return conn_info + + except Exception as e: + self._stats['failed_connections'] += 1 + logger.error(f"Failed to create connection: {e}") + raise + + def _create_sync_connection(self) -> dmPython.Connection: + """Create synchronous database connection (executed in thread pool)""" + try: + connection = dmPython.connect( + self._config['user'], + self._config['password'], + f"{self._config['host']}:{self._config['port']}", + self._config['database'] + ) + + # dmPython connections are in autocommit mode by default + # Transactions are managed explicitly via commit/rollback + + # Set charset if supported + if hasattr(connection, 'set_charset') and self._config.get('charset'): + try: + connection.set_charset(self._config['charset']) + except Exception: + pass # Ignore charset setting failures + + return connection + + except Exception as e: + raise Exception(f"Failed to create dmPython connection: {e}") + + async def acquire(self) -> dmPython.Connection: + """Acquire connection - Supports timeout and connection reuse""" + if self._closed: + raise RuntimeError("Connection pool is closed") + + if not self._initialized: + await self.initialize() + + start_time = time.time() + + while True: + async with self._lock: + # Find available connection + for conn_info in self._connections: + if not conn_info.in_use and conn_info.is_healthy: + # Check if connection is expired + if conn_info.is_expired(self._max_lifetime): + await self._close_connection(conn_info) + continue + + # Check connection health + if await self._is_connection_healthy(conn_info): + conn_info.in_use = True + conn_info.mark_used() + self._stats['active_connections'] = sum( + 1 for c in self._connections if c.in_use + ) + logger.debug( + f"Reused connection - active: {self._stats['active_connections']}" + ) + return conn_info.connection + else: + await self._close_connection(conn_info) + + # If no available connection and not at max, create new one + if len(self._connections) < self._max_size: + try: + conn_info = await self._create_connection() + conn_info.in_use = True + conn_info.mark_used() + self._connections.append(conn_info) + + self._stats['created_connections'] += 1 + self._stats['total_connections'] = len(self._connections) + self._stats['active_connections'] = sum( + 1 for c in self._connections if c.in_use + ) + + logger.debug( + f"Created new connection - total: {len(self._connections)}, " + f"active: {self._stats['active_connections']}" + ) + return conn_info.connection + + except Exception as e: + logger.error(f"Failed to create new connection: {e}") + + # Check timeout + if time.time() - start_time > self._acquire_timeout: + self._stats['acquire_timeouts'] += 1 + raise asyncio.TimeoutError(f"Connection acquire timeout ({self._acquire_timeout} seconds)") + + # Wait before retry + await asyncio.sleep(0.1) + + async def release(self, connection: dmPython.Connection) -> None: + """Release connection back to pool""" + if self._closed: + return + + async with self._lock: + for conn_info in self._connections: + if conn_info.connection == connection: + if conn_info.in_use: + conn_info.in_use = False + self._stats['active_connections'] = sum( + 1 for c in self._connections if c.in_use + ) + logger.debug(f"Released connection - active: {self._stats['active_connections']}") + return + + # If connection not found, it might be a cleaned up connection, close it + logger.warning("Releasing unknown connection, closing directly") + try: + await asyncio.get_event_loop().run_in_executor(None, connection.close) + except Exception: + pass + + async def _is_connection_healthy(self, conn_info: ConnectionInfo) -> bool: + """Check if connection is healthy""" + try: + # Execute simple query to test connection + cursor = await asyncio.get_event_loop().run_in_executor( + None, conn_info.connection.cursor + ) + + await asyncio.get_event_loop().run_in_executor( + None, cursor.execute, "SELECT 1" + ) + + await asyncio.get_event_loop().run_in_executor( + None, cursor.close + ) + + conn_info.is_healthy = True + return True + + except Exception as e: + logger.warning(f"Connection health check failed: {e}") + conn_info.is_healthy = False + return False + + async def _close_connection(self, conn_info: ConnectionInfo) -> None: + """Close single connection""" + try: + if conn_info in self._connections: + self._connections.remove(conn_info) + + await asyncio.get_event_loop().run_in_executor( + None, conn_info.connection.close + ) + + self._stats['closed_connections'] += 1 + self._stats['total_connections'] = len(self._connections) + + logger.debug(f"Closed connection - remaining connections: {len(self._connections)}") + + except Exception as e: + logger.error(f"Error closing connection: {e}") + + async def _health_check_loop(self) -> None: + """Health check loop - Periodically clean up invalid connections""" + while not self._closed: + try: + await asyncio.sleep(self._health_check_interval) + + if self._closed: + break + + await self._perform_health_check() + + except asyncio.CancelledError: + break + except Exception as e: + logger.error(f"Health check loop error: {e}") + + async def _perform_health_check(self) -> None: + """Perform health check - Clean up invalid and idle connections""" + async with self._lock: + connections_to_remove = [] + + for conn_info in self._connections: + if conn_info.in_use: + continue + + # Check if connection is expired or idle too long + if conn_info.is_expired(self._max_lifetime) or \ + conn_info.is_idle_too_long(self._max_idle_time): + connections_to_remove.append(conn_info) + continue + + # Health check + if not await self._is_connection_healthy(conn_info): + connections_to_remove.append(conn_info) + self._stats['health_check_failures'] += 1 + + # Remove invalid connections + for conn_info in connections_to_remove: + await self._close_connection(conn_info) + + # Ensure minimum connections + current_count = len(self._connections) + if current_count < self._min_size: + needed = self._min_size - current_count + logger.info(f"Connection count below minimum, need to create {needed} connections") + + for i in range(needed): + try: + conn_info = await self._create_connection() + self._connections.append(conn_info) + self._stats['created_connections'] += 1 + logger.debug(f"Created connection in health check {i+1}/{needed}") + except Exception as e: + logger.error(f"Failed to create connection in health check: {e}") + break + + self._stats['total_connections'] = len(self._connections) + + if connections_to_remove: + logger.info( + f"Health check completed - removed {len(connections_to_remove)} connections, " + f"current connections: {len(self._connections)}" + ) + + async def get_status(self) -> Dict[str, Any]: + """Get connection pool status""" + async with self._lock: + idle_connections = sum(1 for c in self._connections if not c.in_use) + unhealthy_connections = sum(1 for c in self._connections if not c.is_healthy) + + return { + 'total_connections': len(self._connections), + 'active_connections': self._stats['active_connections'], + 'idle_connections': idle_connections, + 'unhealthy_connections': unhealthy_connections, + 'min_size': self._min_size, + 'max_size': self._max_size, + 'max_idle_time': self._max_idle_time, + 'max_lifetime': self._max_lifetime, + 'statistics': self._stats.copy(), + 'initialized': self._initialized, + 'closed': self._closed, + } + + async def close(self) -> None: + """Close connection pool and all connections""" + if self._closed: + return + + logger.info("Starting connection pool shutdown") + self._closed = True + + # Cancel health check task + if self._health_check_task and not self._health_check_task.done(): + self._health_check_task.cancel() + try: + await self._health_check_task + except asyncio.CancelledError: + pass + + async with self._lock: + # Close all connections + for conn_info in self._connections[:]: + await self._close_connection(conn_info) + + self._connections.clear() + self._stats['total_connections'] = 0 + self._stats['active_connections'] = 0 + + # Shutdown thread pool if we created it + if self._own_executor and self._executor: + self._executor.shutdown(wait=True) + + logger.info("Connection pool closed") + + def __del__(self) -> None: + """Destructor - Ensure resource cleanup""" + if not self._closed: + logger.warning("Connection pool not properly closed, forcing cleanup") + try: + # Try to clean up in event loop + loop = asyncio.get_event_loop() + if loop.is_running(): + loop.create_task(self.close()) + else: + loop.run_until_complete(self.close()) + except Exception: + pass + + async def __aenter__(self) -> "DmConnectionPool": + """Async context manager entry""" + await self.initialize() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + """Async context manager exit""" + await self.close() \ No newline at end of file diff --git a/tortoise/backends/dameng/schema_generator.py b/tortoise/backends/dameng/schema_generator.py new file mode 100644 index 000000000..d577763c2 --- /dev/null +++ b/tortoise/backends/dameng/schema_generator.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type + +from tortoise.backends.base.schema_generator import BaseSchemaGenerator +from tortoise.fields import Field + +from .types import ( + TORTOISE_TO_DM_FIELD_MAP, + get_field_sql_with_params, + quote_identifier, +) + +if TYPE_CHECKING: + from .client import DmClient + +logger = logging.getLogger("tortoise.backends.dameng") + + +class DmSchemaGenerator(BaseSchemaGenerator): + """Dameng database schema generator""" + + DIALECT = "dm" + client: DmClient + + # Use unified type mapping + FIELD_TYPE_MAP = TORTOISE_TO_DM_FIELD_MAP + + def __init__(self, client: DmClient) -> None: + super().__init__(client) + self._foreign_keys: List[str] = [] + + def quote(self, name: str) -> str: + """Quote identifier (Dameng uses double quotes)""" + return quote_identifier(name) + + def _column_comment_generator(self, table: str, column: str, comment: str) -> str: + """Generate column comment SQL - Dameng uses COMMENT ON COLUMN syntax""" + # Dameng doesn't support inline column comments in CREATE TABLE + # Need to execute COMMENT ON COLUMN separately + # Return empty string to avoid syntax error + return "" + + def _table_comment_generator(self, table: str, comment: str) -> str: + """Generate table comment SQL - Dameng uses COMMENT ON TABLE syntax""" + # Dameng doesn't support inline table comments in CREATE TABLE + # Need to execute COMMENT ON TABLE separately + # Return empty string to avoid syntax error + return "" + + def _escape_default_value(self, default: Any) -> str: + """Escape default value - Dameng database default value handling""" + if default is None: + return "NULL" + if isinstance(default, bool): + return "1" if default else "0" + if isinstance(default, str): + # Escape single quotes + escaped = default.replace("'", "''") + return f"'{escaped}'" + if isinstance(default, (int, float)): + return str(default) + # For other types, convert to string + return f"'{str(default)}'" + + def _get_column_sql(self, table: str, column: dict) -> str: + """Generate column definition SQL""" + column_name = self.quote(column["name"]) + column_type = column["type"] + default = column.get("default", "") + nullable = "" if column.get("nullable", True) else " NOT NULL" + unique = " UNIQUE" if column.get("unique", False) else "" + primary_key = " PRIMARY KEY" if column.get("pk", False) else "" + + if default: + default = f" DEFAULT {default}" + + return f"{column_name} {column_type}{primary_key}{unique}{nullable}{default}" + + def _column_type(self, field: Field) -> Optional[str]: + """Get database column type for field - Support full type conversion""" + field_type = field.__class__.__name__ + + # Skip relational fields + if field_type in [ + "ManyToManyField", "ReverseRelation", + "BackwardFKRelation", "BackwardOneToOneRelation" + ]: + return None + + # Special handling for foreign key fields + if field_type in ["ForeignKeyField", "OneToOneField"]: + related_field = getattr(field, "related_model", None) + if related_field: + pk_field = getattr(related_field._meta, "pk_field", None) + if pk_field: + return self._column_type(pk_field) + return "BIGINT" # Default to BIGINT + + # Use type mapping function + column_type = get_field_sql_with_params(field_type, field) + if column_type: + return column_type + + logger.warning(f"Unknown field type: {field_type}, using default VARCHAR(255)") + return "VARCHAR(255)" + + def _create_table_sql(self, table: str, columns: List[dict], comment: str = "") -> str: + """Generate CREATE TABLE SQL""" + columns_sql = [] + for column in columns: + columns_sql.append(self._get_column_sql(table, column)) + + create_sql = f"CREATE TABLE {self.quote(table)} ({', '.join(columns_sql)})" + if comment: + create_sql += f" COMMENT = '{comment}'" + + return create_sql + + def _create_index_sql( + self, table: str, index_name: str, columns: List[str], unique: bool = False + ) -> str: + """Generate CREATE INDEX SQL""" + unique_str = "UNIQUE " if unique else "" + columns_str = ", ".join([self.quote(col) for col in columns]) + return ( + f"CREATE {unique_str}INDEX {self.quote(index_name)} " + f"ON {self.quote(table)} ({columns_str})" + ) + + def _get_index_sql(self, model, field_names: List[str], safe: bool) -> str: + """Override base method to handle Dameng index creation""" + # Dameng doesn't support CREATE INDEX IF NOT EXISTS syntax + # So we need to remove IF NOT EXISTS part + sql = super()._get_index_sql(model, field_names, safe) + if sql and "IF NOT EXISTS" in sql: + sql = sql.replace("IF NOT EXISTS ", "") + return sql + + async def generate_schema_for_client(self, safe: bool = True) -> str: + """Generate schema creation SQL - Complete DDL generation implementation""" + from tortoise import Tortoise + + ddl_statements = [] + + try: + # Get all models + for app_name, models in Tortoise.apps.items(): + for model_name, model_cls in models.items(): + if hasattr(model_cls, "_meta"): + table_name = model_cls._meta.db_table + + # Generate table structure + columns = [] + indexes = [] + foreign_keys = [] + + # Process fields + for field_name, field_obj in model_cls._meta.fields_map.items(): + if field_obj.__class__.__name__ in [ + "ManyToManyField", + "ReverseRelation", + "BackwardFKRelation", + "BackwardOneToOneRelation", + ]: + continue + + column_type = self._column_type(field_obj) + if not column_type: + continue + + # Build column definition + column_def = { + "name": ( + field_obj.model_field_name + if hasattr(field_obj, "model_field_name") + else field_name + ), + "type": column_type, + "pk": field_obj.pk if hasattr(field_obj, "pk") else False, + "nullable": field_obj.null if hasattr(field_obj, "null") else True, + "unique": ( + field_obj.unique if hasattr(field_obj, "unique") else False + ), + "default": getattr(field_obj, "default", None), + } + + columns.append(column_def) + + # Handle indexes + if hasattr(field_obj, "index") and field_obj.index: + indexes.append({ + "name": f"idx_{table_name}_{field_name}", + "columns": [field_name], + "unique": False + }) + + # Handle foreign keys + if field_obj.__class__.__name__ in ["ForeignKeyField", "OneToOneField"]: + related_model = field_obj.related_model + if related_model: + foreign_keys.append({ + "name": f"fk_{table_name}_{field_name}", + "column": field_name, + "reference_table": related_model._meta.db_table, + "reference_column": "id", # Assume primary key is id + }) + + # Generate CREATE TABLE statement + if columns: + table_sql = self._create_table_sql(table_name, columns) + ddl_statements.append(table_sql) + + # Generate index statements + for index in indexes: + index_sql = self._create_index_sql( + table_name, index["name"], + index["columns"], index["unique"] + ) + ddl_statements.append(index_sql) + + # Generate foreign key constraint statements + for fk in foreign_keys: + fk_sql = ( + f"ALTER TABLE {self.quote(table_name)} " + f"ADD CONSTRAINT {self.quote(fk['name'])} " + f"FOREIGN KEY ({self.quote(fk['column'])}) " + f"REFERENCES {self.quote(fk['reference_table'])}" + f"({self.quote(fk['reference_column'])})" + ) + ddl_statements.append(fk_sql) + + return ";\n".join(ddl_statements) + ";" if ddl_statements else "" + + except Exception as e: + logger.error(f"Failed to generate database schema: {e}") + if not safe: + raise + return "" \ No newline at end of file diff --git a/tortoise/backends/dameng/types.py b/tortoise/backends/dameng/types.py new file mode 100644 index 000000000..14e083db5 --- /dev/null +++ b/tortoise/backends/dameng/types.py @@ -0,0 +1,468 @@ +from __future__ import annotations + +import datetime +from decimal import Decimal +from typing import Any, Dict + +# Dameng database keywords and reserved words +DM_KEYWORDS = { + # A + 'ABORT', 'ABSOLUTE', 'ABSTRACT', 'ACCESSED', 'ACCOUNT', 'ACROSS', 'ACTION', 'ADD', 'ADMIN', + 'ADVANCED', 'AFTER', 'AGGREGATE', 'ALL', 'ALLOW_DATETIME', 'ALLOW_IP', 'ALTER', 'ANALYZE', + 'AND', 'ANY', 'APPLY', 'APR', 'ARCHIVE', 'ARCHIVEDIR', 'ARCHIVELOG', 'ARCHIVESTYLE', 'ARRAY', + 'ARRAYLEN', 'AS', 'ASC', 'ASCII', 'ASENSITIVE', 'ASSIGN', 'ASYNCHRONOUS', 'AT', 'ATTACH', + 'AUDIT', 'AUG', 'AUTHID', 'AUTHORIZATION', 'AUTO', 'AUTO_INCREMENT', 'AUTOEXTEND', + 'AUTONOMOUS_TRANSACTION', 'AVG', + + # B + 'BACKED', 'BACKUP', 'BACKUPDIR', 'BACKUPINFO', 'BACKUPSET', 'BADFILE', 'BAKFILE', 'BASE', + 'BATCH', 'BEFORE', 'BEGIN', 'BETWEEN', 'BIGDATEDIFF', 'BIGINT', 'BINARY', 'BIT', 'BITMAP', + 'BLOB', 'BLOCK', 'BOOL', 'BOOLEAN', 'BOTH', 'BRANCH', 'BREADTH', 'BREAK', 'BSTRING', 'BTREE', + 'BUFFER', 'BUILD', 'BULK', 'BY', 'BYDAY', 'BYHOUR', 'BYMINUTE', 'BYMONTH', 'BYMONTHDAY', + 'BYSECOND', 'BYTE', 'BYWEEKNO', 'BYYEARDAY', + + # C + 'CACHE', 'CALCULATE', 'CALL', 'CASCADE', 'CASCADED', 'CASE', 'CASE_SENSITIVE', 'CAST', + 'CATALOG', 'CATCH', 'CHAIN', 'CHANGE', 'CHAR', 'CHARACTER', 'CHARACTERISTICS', 'CHECK', + 'CHECKPOINT', 'CIPHER', 'CLASS', 'CLOB', 'CLOSE', 'CLUSTER', 'CLUSTERBTR', 'COLLATE', + 'COLLATION', 'COLLECT', 'COLUMN', 'COLUMNS', 'COMMENT', 'COMMIT', 'COMMITTED', 'COMMITWORK', + 'COMPILE', 'COMPLETE', 'COMPRESS', 'COMPRESSED', 'CONDITIONAL', 'CONNECT', + 'CONNECT_BY_ISCYCLE', 'CONNECT_BY_ISLEAF', 'CONNECT_BY_ROOT', 'CONNECT_IDLE_TIME', + 'CONNECT_TIME', 'CONST', 'CONSTANT', 'CONSTRAINT', 'CONSTRAINTS', 'CONSTRUCTOR', 'CONTAINS', + 'CONTEXT', 'CONTINUE', 'CONVERT', 'COPY', 'CORRESPONDING', 'CORRUPT', 'COUNT', 'COUNTER', + 'CPU_PER_CALL', 'CPU_PER_SESSION', 'CREATE', 'CROSS', 'CRYPTO', 'CTLFILE', 'CUBE', + 'CUMULATIVE', 'CURRENT', 'CURRENT_SCHEMA', 'CURRENT_USER', 'CURSOR', 'CYCLE', + + # D + 'DAILY', 'DANGLING', 'DATA', 'DATABASE', 'DATAFILE', 'DATE', 'DATEADD', 'DATEDIFF', + 'DATEPART', 'DATETIME', 'DAY', 'DBFILE', 'DDL', 'DDL_CLONE', 'DEBUG', 'DEC', 'DECIMAL', + 'DECLARE', 'DECODE', 'DEFAULT', 'DEFERRABLE', 'DEFERRED', 'DEFINER', 'DELETE', 'DELETING', + 'DELIMITED', 'DELTA', 'DEMAND', 'DENSE_RANK', 'DEPTH', 'DEREF', 'DESC', 'DETACH', + 'DETERMINISTIC', 'DEVICE', 'DIAGNOSTICS', 'DICTIONARY', 'DIRECTORY', 'DISABLE', 'DISCONNECT', + 'DISKGROUP', 'DISKSPACE', 'DISTINCT', 'DISTRIBUTED', 'DML', 'DO', 'DOMAIN', 'DOUBLE', 'DOWN', + 'DROP', 'DUMP', + + # E + 'EACH', 'EDITIONABLE', 'ELSE', 'ELSEIF', 'ELSIF', 'EMPTY', 'ENABLE', 'ENCRYPT', 'ENCRYPTION', + 'END', 'EQU', 'ERROR', 'ERRORS', 'ESCAPE', 'EVALNAME', 'EVENTINFO', 'EVENTS', 'EXCEPT', + 'EXCEPTION', 'EXCEPTION_INIT', 'EXCEPTIONS', 'EXCHANGE', 'EXCLUDE', 'EXCLUDING', 'EXCLUSIVE', + 'EXEC', 'EXECUTE', 'EXISTS', 'EXIT', 'EXPIRE', 'EXPLAIN', 'EXTENDS', 'EXTERN', 'EXTERNAL', + 'EXTERNALLY', 'EXTRACT', + + # F + 'FAILED_LOGIN_ATTEMPS', 'FAILED_LOGIN_ATTEMPTS', 'FAST', 'FEB', 'FETCH', 'FIELDS', 'FILE', + 'FILEGROUP', 'FILESIZE', 'FILLFACTOR', 'FINAL', 'FINALLY', 'FIRST', 'FLASHBACK', 'FLOAT', + 'FOLLOWS', 'FOLLOWING', 'FOR', 'FORALL', 'FORCE', 'FOREIGN', 'FORMAT', 'FREQ', 'FREQUENCE', + 'FRI', 'FROM', 'FULL', 'FULLY', 'FUNCTION', + + # G + 'GENERATED', 'GET', 'GLOBAL', 'GLOBALLY', 'GLOBAL_SESSION_PER_USER', 'GOTO', 'GRANT', + 'GREAT', 'GROUP', 'GROUPING', + + # H + 'HASH', 'HASHPARTMAP', 'HAVING', 'HEXTORAW', 'HIGH', 'HOLD', 'HOUR', 'HOURLY', 'HUGE', + + # I + 'IDENTIFIED', 'IDENTIFIER', 'IDENTITY', 'IDENTITY_INSERT', 'IF', 'IFNULL', + 'IGNORE_ROW_ON_DUPKEY_INDEX', 'IMAGE', 'IMMEDIATE', 'IN', 'INCLUDE', 'INCLUDING', 'INCREASE', + 'INCREMENT', 'INDEX', 'INDEXES', 'INDICES', 'INITIAL', 'INITIALIZED', 'INITIALLY', 'INLINE', + 'INNER', 'INNERID', 'INPUT', 'INSENSITIVE', 'INSERT', 'INSERTING', 'INSTANCE', 'INSTANTIABLE', + 'INSTEAD', 'INT', 'INTEGER', 'INTENT', 'INTERSECT', 'INTERVAL', 'INTO', 'INVISIBLE', 'IS', + 'ISOLATION', 'INACTIVE_ACCOUNT_TIME', + + # J-Z + 'JAN', 'JAVA', 'JOB', 'JOIN', 'JSON', 'JSON_TABLE', 'JUL', 'JUN', + 'KEEP', 'KEY', 'KEYS', + 'LABEL', 'LARGE', 'LAST', 'LAX', 'LEADING', 'LEFT', 'LEFTARG', 'LESS', 'LEVEL', 'LEVELS', + 'LEXER', 'LIKE', 'LIMIT', 'LINK', 'LIST', 'LNNVL', 'LOB', 'LOCAL', 'LOCAL_OBJECT', 'LOCALLY', + 'LOCATION', 'LOCK', 'LOCKED', 'LOG', 'LOGFILE', 'LOGGING', 'LOGIN', 'LOGOFF', 'LOGON', + 'LOGOUT', 'LONG', 'LONGVARBINARY', 'LONGVARCHAR', 'LOOP', 'LSN', + 'MANUAL', 'MAP', 'MAPPED', 'MAR', 'MATCH', 'MATCHED', 'MATERIALIZED', 'MAX', + 'MAX_RUN_DURATION', 'MAXPIECESIZE', 'MAXSIZE', 'MAXVALUE', 'MAY', 'MEM_SPACE', 'MEMBER', + 'MEMORY', 'MERGE', 'MICRO', 'MIN', 'MINEXTENTS', 'MINUS', 'MINUTE', 'MINUTELY', 'MINVALUE', + 'MIRROR', 'MOD', 'MODE', 'MODIFY', 'MON', 'MONEY', 'MONITORING', 'MONTH', 'MONTHLY', 'MOUNT', + 'MOVE', 'MOVEMENT', 'MULTISET', + 'NATIONAL', 'NATURAL', 'NCHAR', 'NCHARACTER', 'NEVER', 'NEW', 'NEXT', 'NO', 'NOARCHIVELOG', + 'NOAUDIT', 'NOBRANCH', 'NOCACHE', 'NOCOPY', 'NOCYCLE', 'NODE', 'NOLOGGING', 'NOMAXVALUE', + 'NOMINVALUE', 'NOMONITORING', 'NONE', 'NONEDITIONABLE', 'NOORDER', 'NOPARALLEL', 'NORMAL', + 'NOROWDEPENDENCIES', 'NOSORT', 'NOT', 'NOT_ALLOW_DATETIME', 'NOT_ALLOW_IP', 'NOV', + 'NOVALIDATE', 'NOWAIT', 'NULL', 'NULLS', 'NUMBER', 'NUMERIC', + 'OBJECT', 'OCT', 'OF', 'OFF', 'OFFLINE', 'OFFSET', 'OIDINDEX', 'OLD', 'ON', 'ONCE', 'ONLINE', + 'ONLY', 'OPEN', 'OPERATOR', 'OPTIMIZE', 'OPTION', 'OR', 'ORDER', 'ORDINALITY', 'OUT', 'OUTER', + 'OVER', 'OVERLAPS', 'OVERLAY', 'OVERRIDE', 'OVERRIDING', + 'PACKAGE', 'PAD', 'PAGE', 'PARALLEL', 'PARALLEL_ENABLE', 'PARMS', 'PARTIAL', 'PARTITION', + 'PARTITIONS', 'PASSING', 'PASSWORD', 'PASSWORD_GRACE_TIME', 'PASSWORD_LIFE_TIME', + 'PASSWORD_LOCK_TIME', 'PASSWORD_POLICY', 'PASSWORD_REUSE_MAX', 'PASSWORD_REUSE_TIME', 'PATH', + 'PENDANT', 'PERCENT', 'PIPE', 'PIPELINED', 'PIVOT', 'PLACING', 'PLS_INTEGER', 'PRAGMA', + 'PREBUILT', 'PRECEDES', 'PRECEDING', 'PRECISION', 'PRESERVE', 'PRETTY', 'PRIMARY', 'PRINT', + 'PRIOR', 'PRIVATE', 'PRIVILEGE', 'PRIVILEGES', 'PROCEDURE', 'PROFILE', 'PROTECTED', 'PUBLIC', + 'PURGE', + 'QUERY_REWRITE_INTEGRITY', 'QUOTA', + 'RAISE', 'RANDOMLY', 'RANGE', 'RAWTOHEX', 'READ', 'READ_PER_CALL', 'READ_PER_SESSION', + 'READONLY', 'REAL', 'REBUILD', 'RECORD', 'RECORDS', 'REDUCED', 'REF', 'REFERENCE', + 'REFERENCES', 'REFERENCING', 'REFRESH', 'REJECT', 'RELATED', 'RELATIVE', 'RELEASE', 'RENAME', + 'REPEAT', 'REPEATABLE', 'REPLACE', 'REPLAY', 'REPLICATE', 'RESIZE', 'RESTORE', 'RESTRICT', + 'RESTRICT_REFERENCES', 'RESULT', 'RESULT_CACHE', 'RETURN', 'RETURNING', 'REVERSE', 'REVOKE', + 'RIGHT', 'RIGHTARG', 'ROLE', 'ROLLBACK', 'ROLLFILE', 'ROLLUP', 'ROOT', 'ROW', 'ROWCOUNT', + 'ROWDEPENDENCIES', 'ROWID', 'ROWNUM', 'ROWS', 'RULE', + 'SALT', 'SAMPLE', 'SAT', 'SAVE', 'SAVEPOINT', 'SBYTE', 'SCHEMA', 'SCHEMABINDING', 'SCN', + 'SCOPE', 'SCROLL', 'SEALED', 'SEARCH', 'SECOND', 'SECONDLY', 'SECTION', 'SEED', 'SELECT', + 'SELF', 'SENSITIVE', 'SEP', 'SEQUENCE', 'SERERR', 'SERIALIZABLE', 'SERVER', 'SESSION', + 'SESSION_PER_USER', 'SET', 'SETS', 'SHADOW', 'SHARE', 'SHORT', 'SHUTDOWN', 'SIBLINGS', + 'SIMPLE', 'SINCE', 'SIZE', 'SIZEOF', 'SKIP', 'SMALLINT', 'SNAPSHOT', 'SOME', 'SOUND', 'SPACE', + 'SPAN', 'SPATIAL', 'SPEED', 'SPFILE', 'SPLIT', 'SQL', 'STANDBY', 'STARTUP', 'STAT', + 'STATEMENT', 'STATIC', 'STDDEV', 'STOP', 'STORAGE', 'STORE', 'STRICT', 'STRING', 'STRIPING', + 'STRUCT', 'STYLE', 'SUBPARTITION', 'SUBPARTITIONS', 'SUBSCRIBE', 'SUBSTITUTABLE', 'SUBSTRING', + 'SUBTYPE', 'SUCCESSFUL', 'SUM', 'SUN', 'SUSPEND', 'SWITCH', 'SYNC', 'SYNCHRONOUS', 'SYNONYM', + 'SYS_CONNECT_BY_PATH', 'SYSTEM', + 'TABLE', 'TABLESPACE', 'TASK', 'TEMPLATE', 'TEMPORARY', 'TEXT', 'THAN', 'THEN', 'THREAD', + 'THROUGH', 'THROW', 'THU', 'TIES', 'TIME', 'TIME_ZONE', 'TIMER', 'TIMES', 'TIMESTAMP', + 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TINYINT', 'TO', 'TOP', 'TRACE', 'TRACKING', 'TRAILING', + 'TRANSACTION', 'TRANSACTIONAL', 'TRIGGER', 'TRIGGERS', 'TRIM', 'TRUNCATE', 'TRUNCSIZE', + 'TRXID', 'TRY', 'TUE', 'TYPE', 'TYPEDEF', 'TYPEOF', + 'UINT', 'ULONG', 'UNBOUNDED', 'UNCOMMITTED', 'UNCONDITIONAL', 'UNDER', 'UNION', 'UNIQUE', + 'UNLIMITED', 'UNLOCK', 'UNPIVOT', 'UNTIL', 'UNUSABLE', 'UP', 'UPDATE', 'UPDATING', 'USAGE', + 'USE_HASH', 'USE_MERGE', 'USE_NL', 'USE_NL_WITH_INDEX', 'USER', 'USHORT', 'USING', + 'VALUE', 'VALUES', 'VALIDATE', 'VARBINARY', 'VARCHAR', 'VARCHAR2', 'VARIANCE', 'VARRAY', + 'VARYING', 'VERIFY', 'VERSIONS', 'VERSIONS_ENDTIME', 'VERSIONS_ENDTRXID', 'VERSIONS_OPERATION', + 'VERSIONS_STARTTIME', 'VERSIONS_STARTTRXID', 'VERTICAL', 'VIEW', 'VIRTUAL', 'VISIBLE', 'VOID', + 'VSIZE', + 'WAIT', 'WED', 'WEEK', 'WEEKLY', 'WHEN', 'WHENEVER', 'WHERE', 'WHILE', 'WITH', 'WITHIN', + 'WITHOUT', 'WORK', 'WRAPPED', 'WRAPPER', 'WRITE', + 'XML', 'XMLAGG', 'XMLATTRIBUTES', 'XMLELEMENT', 'XMLPARSE', 'XMLTABLE', 'XMLNAMESPACES', + 'XMLSERIALIZE', + 'YEAR', 'YEARLY', + 'ZONE', +} + + +# Dameng database type to Tortoise ORM field type mapping +DM_TO_TORTOISE_FIELD_MAP: Dict[str, str] = { + # String types + 'CHAR': 'CharField', + 'CHARACTER': 'CharField', + 'VARCHAR': 'CharField', + 'VARCHAR2': 'CharField', + 'ROWID': 'CharField', + 'TEXT': 'TextField', + 'LONG': 'TextField', + 'LONGVARCHAR': 'TextField', + 'CLOB': 'TextField', + # Integer types + 'TINYINT': 'SmallIntField', + 'BYTE': 'SmallIntField', + 'SMALLINT': 'SmallIntField', + 'INTEGER': 'IntField', + 'INT': 'IntField', + 'BIGINT': 'BigIntField', + # Float types + 'FLOAT': 'FloatField', + 'REAL': 'FloatField', + 'DOUBLE': 'FloatField', + 'DOUBLE PRECISION': 'FloatField', + # Decimal types + 'NUMERIC': 'DecimalField', + 'DECIMAL': 'DecimalField', + 'DEC': 'DecimalField', + 'NUMBER': 'DecimalField', + # Binary types + 'BINARY': 'BinaryField', + 'VARBINARY': 'BinaryField', + 'RAW': 'BinaryField', + 'IMAGE': 'BinaryField', + 'LONGVARBINARY': 'BinaryField', + 'BLOB': 'BinaryField', + 'BFILE': 'CharField', # Stores file path + # Date/time types + 'DATE': 'DateField', + 'TIME': 'TimeField', + 'TIMESTAMP': 'DatetimeField', + 'DATETIME': 'DatetimeField', + 'TIME WITH TIME ZONE': 'TimeField', + 'TIMESTAMP WITH TIME ZONE': 'DatetimeField', + 'DATETIME WITH TIME ZONE': 'DatetimeField', + 'TIMESTAMP WITH LOCAL TIME ZONE': 'DatetimeField', + 'DATETIME WITH LOCAL TIME ZONE': 'DatetimeField', + # Interval types + 'INTERVAL YEAR TO MONTH': 'CharField', + 'INTERVAL YEAR': 'CharField', + 'INTERVAL MONTH': 'CharField', + 'INTERVAL DAY': 'TimeDeltaField', + 'INTERVAL DAY TO HOUR': 'TimeDeltaField', + 'INTERVAL DAY TO MINUTE': 'TimeDeltaField', + 'INTERVAL DAY TO SECOND': 'TimeDeltaField', + 'INTERVAL HOUR': 'TimeDeltaField', + 'INTERVAL HOUR TO MINUTE': 'TimeDeltaField', + 'INTERVAL HOUR TO SECOND': 'TimeDeltaField', + 'INTERVAL MINUTE': 'TimeDeltaField', + 'INTERVAL MINUTE TO SECOND': 'TimeDeltaField', + 'INTERVAL SECOND': 'TimeDeltaField', + # Other types + 'BIT': 'BooleanField', +} + + +# Tortoise ORM field type to Dameng database type mapping +TORTOISE_TO_DM_FIELD_MAP: Dict[str, str] = { + 'BigIntField': 'BIGINT', + 'BinaryField': 'BLOB', + 'BooleanField': 'BIT', + 'CharField': 'VARCHAR', + 'DateField': 'DATE', + 'DatetimeField': 'TIMESTAMP', + 'DecimalField': 'DECIMAL', + 'FloatField': 'DOUBLE', + 'IntField': 'INT', + 'JSONField': 'CLOB', + 'SmallIntField': 'SMALLINT', + 'TextField': 'CLOB', + 'TimeField': 'TIME', + 'TimeDeltaField': 'BIGINT', # Store as milliseconds + 'UUIDField': 'VARCHAR(36)', +} + + +# Data type conversion functions +def dm_type_to_python(dm_type: str, value: Any) -> Any: + """Convert Dameng database value to Python type. + + Args: + dm_type: Dameng database field type + value: Database returned value + + Returns: + Converted Python value + """ + if value is None: + return None + + dm_type = dm_type.upper() + + # String types + if dm_type in ( + 'CHAR', 'CHARACTER', 'VARCHAR', 'VARCHAR2', 'ROWID', + 'TEXT', 'LONG', 'LONGVARCHAR', 'CLOB' + ): + return str(value) + + # Integer types + elif dm_type in ('TINYINT', 'BYTE', 'SMALLINT', 'INTEGER', 'INT', 'BIGINT'): + return int(value) + + # Float types + elif dm_type in ('FLOAT', 'REAL', 'DOUBLE', 'DOUBLE PRECISION'): + return float(value) + + # Decimal types + elif dm_type in ('NUMERIC', 'DECIMAL', 'DEC', 'NUMBER'): + return Decimal(str(value)) + + # Boolean type + elif dm_type == 'BIT': + return bool(value) + + # Binary types + elif dm_type in ( + 'BINARY', 'VARBINARY', 'RAW', 'IMAGE', 'LONGVARBINARY', 'BLOB' + ): + return bytes(value) if not isinstance(value, bytes) else value + + # Date/time types + elif dm_type == 'DATE': + if isinstance(value, datetime.date): + return value + elif isinstance(value, datetime.datetime): + return value.date() + else: + return datetime.datetime.strptime(str(value), '%Y-%m-%d').date() + + elif dm_type == 'TIME' or dm_type == 'TIME WITH TIME ZONE': + if isinstance(value, datetime.time): + return value + elif isinstance(value, datetime.datetime): + return value.time() + else: + return datetime.datetime.strptime(str(value), '%H:%M:%S').time() + + elif dm_type in ( + 'TIMESTAMP', 'DATETIME', 'TIMESTAMP WITH TIME ZONE', + 'DATETIME WITH TIME ZONE', 'TIMESTAMP WITH LOCAL TIME ZONE', + 'DATETIME WITH LOCAL TIME ZONE' + ): + if isinstance(value, datetime.datetime): + return value + else: + return datetime.datetime.strptime(str(value), '%Y-%m-%d %H:%M:%S') + + # Interval types + elif dm_type.startswith('INTERVAL'): + # Return string representation, application layer needs further processing + return str(value) + + # BFILE - file path + elif dm_type == 'BFILE': + return str(value) + + # Default return original value + return value + + +def python_to_dm_type(python_value: Any, dm_type: str) -> Any: + """Convert Python type to Dameng database acceptable value. + + Args: + python_value: Python value + dm_type: Target Dameng database type + + Returns: + Converted database value + """ + if python_value is None: + return None + + dm_type = dm_type.upper() + + # String types + if dm_type in ( + 'CHAR', 'CHARACTER', 'VARCHAR', 'VARCHAR2', 'ROWID', + 'TEXT', 'LONG', 'LONGVARCHAR', 'CLOB', 'BFILE' + ): + return str(python_value) + + # Integer types + elif dm_type in ('TINYINT', 'BYTE', 'SMALLINT', 'INTEGER', 'INT', 'BIGINT'): + return int(python_value) + + # Float types + elif dm_type in ('FLOAT', 'REAL', 'DOUBLE', 'DOUBLE PRECISION'): + return float(python_value) + + # Decimal types + elif dm_type in ('NUMERIC', 'DECIMAL', 'DEC', 'NUMBER'): + if isinstance(python_value, Decimal): + return python_value + else: + return Decimal(str(python_value)) + + # Boolean type + elif dm_type == 'BIT': + return 1 if python_value else 0 + + # Binary types + elif dm_type in ( + 'BINARY', 'VARBINARY', 'RAW', 'IMAGE', 'LONGVARBINARY', 'BLOB' + ): + if isinstance(python_value, bytes): + return python_value + else: + return bytes(python_value, 'utf-8') if isinstance(python_value, str) else bytes(python_value) + + # Date types + elif dm_type == 'DATE': + if isinstance(python_value, datetime.date): + return python_value + elif isinstance(python_value, datetime.datetime): + return python_value.date() + + # Time types + elif dm_type in ('TIME', 'TIME WITH TIME ZONE'): + if isinstance(python_value, datetime.time): + return python_value + elif isinstance(python_value, datetime.datetime): + return python_value.time() + + # Timestamp types + elif dm_type in ( + 'TIMESTAMP', 'DATETIME', 'TIMESTAMP WITH TIME ZONE', + 'DATETIME WITH TIME ZONE', 'TIMESTAMP WITH LOCAL TIME ZONE', + 'DATETIME WITH LOCAL TIME ZONE' + ): + if isinstance(python_value, datetime.datetime): + return python_value + elif isinstance(python_value, datetime.date): + return datetime.datetime.combine(python_value, datetime.time()) + + # Interval types - TimeDelta to seconds or days + elif dm_type.startswith('INTERVAL'): + if isinstance(python_value, datetime.timedelta): + if 'DAY' in dm_type: + return python_value.days + elif 'HOUR' in dm_type or 'MINUTE' in dm_type or 'SECOND' in dm_type: + return int(python_value.total_seconds()) + else: + return str(python_value) + return str(python_value) + + # Default return original value + return python_value + + +def is_dm_keyword(identifier: str) -> bool: + """Check if identifier is a Dameng database keyword or reserved word. + + Args: + identifier: Identifier to check + + Returns: + True if keyword, False otherwise + """ + return identifier.upper() in DM_KEYWORDS + + +def quote_identifier(identifier: str) -> str: + """Quote identifier, add double quotes if keyword or contains special characters. + + Args: + identifier: Identifier to quote + + Returns: + Quoted identifier + """ + # Dameng database uses double quotes for identifiers + if is_dm_keyword(identifier) or not identifier.isidentifier(): + return f'"{identifier.upper()}"' + return f'"{identifier.upper()}"' # Dameng identifiers are uppercase by default + + +# Data type length and precision mapping +DM_TYPE_LENGTH_MAP: Dict[str, Dict[str, Any]] = { + # String types + 'CHAR': {'max_length': 32767, 'default_length': 1}, + 'VARCHAR': {'max_length': 32767, 'default_length': 255}, + 'VARCHAR2': {'max_length': 32767, 'default_length': 255}, + # Numeric types + 'DECIMAL': {'max_precision': 38, 'max_scale': 127, 'default_precision': 10, 'default_scale': 2}, + 'NUMERIC': {'max_precision': 38, 'max_scale': 127, 'default_precision': 10, 'default_scale': 2}, + 'NUMBER': {'max_precision': 38, 'max_scale': 127, 'default_precision': 10, 'default_scale': 2}, + # Binary types + 'BINARY': {'max_length': 32767, 'default_length': 1}, + 'VARBINARY': {'max_length': 32767, 'default_length': 255}, + 'RAW': {'max_length': 32767, 'default_length': 255}, +} + + +def get_field_sql_with_params(field_type: str, field_obj: Any) -> str: + """Generate SQL type definition with parameters based on field object. + + Args: + field_type: Tortoise ORM field type + field_obj: Field object + + Returns: + SQL type definition string + """ + base_type = TORTOISE_TO_DM_FIELD_MAP.get(field_type, 'VARCHAR') + + # Handle types that need length parameters + if field_type == 'CharField' and hasattr(field_obj, 'max_length'): + return f"VARCHAR({field_obj.max_length})" + + elif field_type == 'DecimalField': + max_digits = getattr(field_obj, 'max_digits', 10) + decimal_places = getattr(field_obj, 'decimal_places', 2) + return f"DECIMAL({max_digits},{decimal_places})" + + elif field_type == 'BinaryField' and hasattr(field_obj, 'max_length'): + return f"VARBINARY({field_obj.max_length})" + + return base_type \ No newline at end of file diff --git a/tortoise/contrib/dameng/__init__.py b/tortoise/contrib/dameng/__init__.py new file mode 100644 index 000000000..620a204eb --- /dev/null +++ b/tortoise/contrib/dameng/__init__.py @@ -0,0 +1 @@ +"""Dameng specific contrib module""" \ No newline at end of file diff --git a/tortoise/contrib/dameng/fields.py b/tortoise/contrib/dameng/fields.py new file mode 100644 index 000000000..44d5a1b0a --- /dev/null +++ b/tortoise/contrib/dameng/fields.py @@ -0,0 +1,93 @@ +"""Dameng-specific field types. + +This module contains field types that are specific to Dameng database. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from tortoise.fields import Field + +if TYPE_CHECKING: # pragma: nocoverage + from tortoise.models import Model + + +class XMLField(Field): + """ + XML Field for storing XML data in Dameng database. + + Dameng has native XML support with the XMLTYPE data type. + """ + + SQL_TYPE = "XMLTYPE" + field_type = str + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + + def to_python_value(self, value: Any) -> Any: + """Convert database value to Python string.""" + if value is None: + return None + return str(value) + + def to_db_value(self, value: Any, instance: "Model") -> Any: + """Convert Python value to database value.""" + if value is None: + return None + return str(value) + + +class IntervalField(Field): + """ + Interval Field for storing time intervals in Dameng database. + + Dameng supports various INTERVAL types for storing time durations. + + Parameters: + interval_type: Type of interval (e.g., 'DAY', 'HOUR', 'DAY TO SECOND') + """ + + field_type = str + + def __init__(self, interval_type: str = "DAY TO SECOND", **kwargs: Any) -> None: + super().__init__(**kwargs) + self.interval_type = interval_type.upper() + + @property + def SQL_TYPE(self) -> str: # type: ignore + """Return the SQL type for this interval field.""" + return f"INTERVAL {self.interval_type}" + + def to_python_value(self, value: Any) -> Any: + """Convert database interval to Python timedelta or string.""" + if value is None: + return None + # Dameng intervals are returned as strings, keep them as is + # Application can parse as needed + return str(value) + + def to_db_value(self, value: Any, instance: "Model") -> Any: + """Convert Python value to database interval.""" + if value is None: + return None + return str(value) + + +class RowIDField(Field): + """ + ROWID Field for accessing Dameng's internal row identifier. + + This is a read-only field that provides access to Dameng's ROWID pseudocolumn. + """ + + SQL_TYPE = "ROWID" + field_type = str + + def __init__(self, **kwargs: Any) -> None: + # ROWID is always read-only + kwargs['pk'] = False + kwargs['null'] = True + kwargs['generated'] = True + super().__init__(**kwargs) \ No newline at end of file diff --git a/tortoise/contrib/dameng/functions.py b/tortoise/contrib/dameng/functions.py new file mode 100644 index 000000000..64c9fa888 --- /dev/null +++ b/tortoise/contrib/dameng/functions.py @@ -0,0 +1,273 @@ +"""Dameng-specific SQL functions. + +This module contains SQL functions that are specific to Dameng database. +""" + +from __future__ import annotations + +from typing import Any, Optional + +from pypika.functions import Function +from pypika.terms import Term + +from tortoise.expressions import F + + +class ToChar(Function): + """ + TO_CHAR function converts a datetime or numeric value to string. + + Example: + await Model.filter( + id=ToChar(F("created_at"), "YYYY-MM-DD") + ) + """ + + def __init__(self, field: Term, format_string: str) -> None: + super().__init__("TO_CHAR", field, format_string) + + +class ToDate(Function): + """ + TO_DATE function converts a string to date. + + Example: + await Model.filter( + created_at__gt=ToDate("2023-01-01", "YYYY-MM-DD") + ) + """ + + def __init__(self, date_string: str, format_string: str) -> None: + super().__init__("TO_DATE", date_string, format_string) + + +class ToNumber(Function): + """ + TO_NUMBER function converts a string to number. + + Example: + await Model.filter( + amount__gt=ToNumber("1000.50") + ) + """ + + def __init__(self, value: str, format_string: str = None) -> None: + if format_string: + super().__init__("TO_NUMBER", value, format_string) + else: + super().__init__("TO_NUMBER", value) + + +class SysDate(Function): + """ + SYSDATE function returns the current system date. + + Example: + await Model.filter( + created_at__lt=SysDate() + ) + """ + + def __init__(self) -> None: + super().__init__("SYSDATE") + + +class AddMonths(Function): + """ + ADD_MONTHS function adds months to a date. + + Example: + await Model.filter( + expiry_date__gt=AddMonths(F("created_at"), 12) + ) + """ + + def __init__(self, date_field: Term, months: int) -> None: + super().__init__("ADD_MONTHS", date_field, months) + + +class LastDay(Function): + """ + LAST_DAY function returns the last day of the month. + + Example: + await Model.filter( + due_date=LastDay(F("created_at")) + ) + """ + + def __init__(self, date_field: Term) -> None: + super().__init__("LAST_DAY", date_field) + + +class MonthsBetween(Function): + """ + MONTHS_BETWEEN function returns the number of months between two dates. + + Example: + await Model.filter( + duration__gt=MonthsBetween(F("end_date"), F("start_date")) + ) + """ + + def __init__(self, date1: Term, date2: Term) -> None: + super().__init__("MONTHS_BETWEEN", date1, date2) + + +class NextDay(Function): + """ + NEXT_DAY function returns the next occurrence of a weekday. + + Example: + await Model.filter( + meeting_date=NextDay(F("created_at"), "MONDAY") + ) + """ + + def __init__(self, date_field: Term, weekday: str) -> None: + super().__init__("NEXT_DAY", date_field, weekday) + + +class Decode(Function): + """ + DECODE function provides if-then-else logic. + + Example: + await Model.annotate( + status_text=Decode( + F("status"), + 1, "Active", + 2, "Inactive", + "Unknown" + ) + ) + """ + + def __init__(self, field: Term, *args) -> None: + super().__init__("DECODE", field, *args) + + +class NVL(Function): + """ + NVL function replaces NULL with a default value. + + Example: + await Model.annotate( + name_display=NVL(F("name"), "Unnamed") + ) + """ + + def __init__(self, field: Term, default_value: Any) -> None: + super().__init__("NVL", field, default_value) + + +class NVL2(Function): + """ + NVL2 function returns one value if not NULL, another if NULL. + + Example: + await Model.annotate( + has_email=NVL2(F("email"), "Yes", "No") + ) + """ + + def __init__(self, field: Term, not_null_value: Any, null_value: Any) -> None: + super().__init__("NVL2", field, not_null_value, null_value) + + +class InitCap(Function): + """ + INITCAP function capitalizes the first letter of each word. + + Example: + await Model.annotate( + title_case=InitCap(F("name")) + ) + """ + + def __init__(self, field: Term) -> None: + super().__init__("INITCAP", field) + + +class Instr(Function): + """ + INSTR function finds the position of a substring. + + Example: + await Model.annotate( + at_pos=Instr(F("email"), "@") + ) + """ + + def __init__(self, string: Term, substring: Term, + start_pos: Optional[int] = None, occurrence: Optional[int] = None) -> None: + args = [string, substring] + if start_pos is not None: + args.append(start_pos) + if occurrence is not None: + args.append(occurrence) + super().__init__("INSTR", *args) + + +class LPad(Function): + """ + LPAD function left-pads a string to a specified length. + + Example: + await Model.annotate( + padded_id=LPad(F("id"), 5, "0") + ) + """ + + def __init__(self, field: Term, length: int, pad_string: str) -> None: + super().__init__("LPAD", field, length, pad_string) + + +class RPad(Function): + """ + RPAD function right-pads a string to a specified length. + + Example: + await Model.annotate( + padded_name=RPad(F("name"), 20, " ") + ) + """ + + def __init__(self, field: Term, length: int, pad_string: str) -> None: + super().__init__("RPAD", field, length, pad_string) + + +class RowNum(Function): + """ + ROWNUM returns the row number in the result set. + + Example: + await Model.filter( + RowNum() <= 10 + ) + """ + + def __init__(self) -> None: + super().__init__("ROWNUM") + + +class ListAgg(Function): + """ + LISTAGG aggregates values into a delimited string. + + Note: Although this is an aggregate function, we inherit from Function + to have better control over the function name and parameters. + + Example: + await Model.annotate( + names=ListAgg(F("name"), ",") + ).group_by("department") + """ + + def __init__(self, field: Term, delimiter: str, order_by: Optional[Term] = None) -> None: + if order_by: + # LISTAGG(field, delimiter) WITHIN GROUP (ORDER BY order_by) + super().__init__("LISTAGG", field, delimiter) + # Note: ORDER BY handling would need to be implemented in the SQL generator + else: + super().__init__("LISTAGG", field, delimiter) \ No newline at end of file diff --git a/tortoise/contrib/sanic/__init__.py b/tortoise/contrib/sanic/__init__.py index d2812e32e..c2452a33e 100644 --- a/tortoise/contrib/sanic/__init__.py +++ b/tortoise/contrib/sanic/__init__.py @@ -1,21 +1,49 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Callable, Iterable from types import ModuleType +from typing import Dict, Optional, Union -from sanic import Sanic # pylint: disable=E0401 +from sanic import Sanic, Request, HTTPResponse # pylint: disable=E0401 +from sanic.response import json from tortoise import Tortoise, connections +from tortoise.exceptions import DoesNotExist, IntegrityError from tortoise.log import logger +def tortoise_exception_handlers() -> Dict[type[Exception], Callable]: + """Create exception handlers for Tortoise ORM exceptions. + + Returns: + Dictionary mapping exception types to handler functions + """ + async def doesnotexist_exception_handler(request: Request, exc: DoesNotExist) -> HTTPResponse: + return json({"detail": str(exc)}, status=404) + + async def integrityerror_exception_handler(request: Request, exc: IntegrityError) -> HTTPResponse: + return json( + {"detail": [{"loc": [], "msg": str(exc), "type": "IntegrityError"}]}, + status=422 + ) + + return { + DoesNotExist: doesnotexist_exception_handler, + IntegrityError: integrityerror_exception_handler, + } + + def register_tortoise( app: Sanic, - config: dict | None = None, - config_file: str | None = None, - db_url: str | None = None, - modules: dict[str, Iterable[str | ModuleType]] | None = None, + config: Optional[Dict] = None, + config_file: Optional[str] = None, + db_url: Optional[str] = None, + modules: Optional[Dict[str, Iterable[Union[str, ModuleType]]]] = None, generate_schemas: bool = False, + add_exception_handlers: bool = False, + use_tz: bool = False, + timezone: str = "UTC", + _create_db: bool = False, ) -> None: """ Registers ``before_server_start`` and ``after_server_stop`` hooks to set-up and tear-down @@ -72,6 +100,15 @@ def register_tortoise( generate_schemas: True to generate schema immediately. Only useful for dev environments or SQLite ``:memory:`` databases + add_exception_handlers: + True to add some automatic exception handlers for ``DoesNotExist`` & ``IntegrityError``. + This is not recommended for production systems as it may leak data. + use_tz: + A boolean that specifies if datetime will be timezone-aware by default or not. + timezone: + Timezone to use, default is UTC. + _create_db: + If True, tries to create database automatically. Raises ------ @@ -80,7 +117,15 @@ def register_tortoise( """ async def tortoise_init() -> None: - await Tortoise.init(config=config, config_file=config_file, db_url=db_url, modules=modules) + await Tortoise.init( + config=config, + config_file=config_file, + db_url=db_url, + modules=modules, + use_tz=use_tz, + timezone=timezone, + _create_db=_create_db, + ) logger.info("Tortoise-ORM started, %s, %s", connections._get_storage(), Tortoise.apps) # pylint: disable=W0212 if generate_schemas: @@ -102,3 +147,7 @@ async def init_orm(app): async def close_orm(app): # pylint: disable=W0612 await connections.close_all() logger.info("Tortoise-ORM shutdown") + + if add_exception_handlers: + for exc_type, handler in tortoise_exception_handlers().items(): + app.error_handler.add(exc_type, handler) diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..c1df8ed54 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1007 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" + +[[package]] +name = "aiomysql" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pymysql" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/76/2c5b55e4406a1957ffdfd933a94c2517455291c97d2b81cec6813754791a/aiomysql-0.2.0.tar.gz", hash = "sha256:558b9c26d580d08b8c5fd1be23c5231ce3aeff2dadad989540fee740253deb67", size = 114706, upload-time = "2023-06-11T19:57:53.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/87/c982ee8b333c85b8ae16306387d703a1fcdfc81a2f3f15a24820ab1a512d/aiomysql-0.2.0-py3-none-any.whl", hash = "sha256:b7c26da0daf23a5ec5e0b133c03d20657276e4eae9b73e040b72787f6f6ade0a", size = 44215, upload-time = "2023-06-11T19:57:51.09Z" }, +] + +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "asyncmy" +version = "0.2.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/76/55cc0577f9e838c5a5213bf33159b9e484c9d9820a2bafd4d6bfa631bf86/asyncmy-0.2.10.tar.gz", hash = "sha256:f4b67edadf7caa56bdaf1c2e6cf451150c0a86f5353744deabe4426fe27aff4e", size = 63889, upload-time = "2024-12-12T14:45:09.2Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/c9/412b137c52f6c6437faba27412ccb32721571c42e59bc4f799796316866b/asyncmy-0.2.10-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:c2237c8756b8f374099bd320c53b16f7ec0cee8258f00d72eed5a2cd3d251066", size = 1803880, upload-time = "2024-12-13T02:36:20.194Z" }, + { url = "https://files.pythonhosted.org/packages/74/f3/c9520f489dc42a594c8ad3cbe2088ec511245a3c55c3333e6fa949838420/asyncmy-0.2.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6e98d4fbf7ea0d99dfecb24968c9c350b019397ba1af9f181d51bb0f6f81919b", size = 1736363, upload-time = "2024-12-13T02:36:41.578Z" }, + { url = "https://files.pythonhosted.org/packages/52/9c/3c531a414290cbde9313cad54bb525caf6b1055ffa56bb271bf70512b533/asyncmy-0.2.10-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b1b1ee03556c7eda6422afc3aca132982a84706f8abf30f880d642f50670c7ed", size = 4970043, upload-time = "2024-12-13T02:35:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/03/64/176ed8a79d3a24b2e8ba7a11b429553f29fea20276537651526f3a87660b/asyncmy-0.2.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e2b97672ea3f0b335c0ffd3da1a5727b530f82f5032cd87e86c3aa3ac6df7f3", size = 5168645, upload-time = "2024-12-13T02:35:50.999Z" }, + { url = "https://files.pythonhosted.org/packages/81/3f/46f126663649784ab6586bc9b482bca432a35588714170621db8d33d76e4/asyncmy-0.2.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c6471ce1f9ae1e6f0d55adfb57c49d0bcf5753a253cccbd33799ddb402fe7da2", size = 4988493, upload-time = "2024-12-13T02:35:54.062Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c6/acce7ea4b74e092582d65744418940b2b8c661102a22a638f58e7b651c6f/asyncmy-0.2.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10e2a10fe44a2b216a1ae58fbdafa3fed661a625ec3c030c560c26f6ab618522", size = 5158496, upload-time = "2024-12-13T02:35:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/d5/01/d8fa0291083e9a0d899addda1f7608da37d28fff9bb4df1bd6f7f37354db/asyncmy-0.2.10-cp310-cp310-win32.whl", hash = "sha256:a791ab117787eb075bc37ed02caa7f3e30cca10f1b09ec7eeb51d733df1d49fc", size = 1624372, upload-time = "2024-12-13T02:36:14.158Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a0/ad6669fd2870492749c189a72c881716a3727b7f0bc972fc8cea7a40879c/asyncmy-0.2.10-cp310-cp310-win_amd64.whl", hash = "sha256:bd16fdc0964a4a1a19aec9797ca631c3ff2530013fdcd27225fc2e48af592804", size = 1694174, upload-time = "2024-12-13T02:36:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/72/1a/21b4af0d19862cc991f1095f006981a4f898599060dfa59f136e292b3e9a/asyncmy-0.2.10-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:7af0f1f31f800a8789620c195e92f36cce4def68ee70d625534544d43044ed2a", size = 1806974, upload-time = "2024-12-13T02:36:23.375Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/3579a88123ead38e60e0b6e744224907e3d7a668518f9a46ed584df4f788/asyncmy-0.2.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:800116ab85dc53b24f484fb644fefffac56db7367a31e7d62f4097d495105a2c", size = 1738218, upload-time = "2024-12-13T02:36:44.753Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/10646bbafce22025be25aa709e83f0cdd3fb9089304cf9d3169a80540850/asyncmy-0.2.10-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:39525e9d7e557b83db268ed14b149a13530e0d09a536943dba561a8a1c94cc07", size = 5346417, upload-time = "2024-12-13T02:36:00.17Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f8/3fb0d0481def3a0900778f7d04f50028a4a2d987087a2f1e718e6c236e01/asyncmy-0.2.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76e199d6b57918999efc702d2dbb182cb7ba8c604cdfc912517955219b16eaea", size = 5553197, upload-time = "2024-12-13T02:36:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/82/a5/8281e8c0999fc6303b5b522ee82d1e338157a74f8bbbaa020e392b69156a/asyncmy-0.2.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ca8fdd7dbbf2d9b4c2d3a5fac42b058707d6a483b71fded29051b8ae198a250", size = 5337915, upload-time = "2024-12-13T02:36:09.126Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/425108f5c6976ceb67b8f95bc73480fe777a95e7a89a29299664f5cb380f/asyncmy-0.2.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0df23db54e38602c803dacf1bbc1dcc4237a87223e659681f00d1a319a4f3826", size = 5524662, upload-time = "2024-12-13T02:36:12.525Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/17291b12dce380abbbec888ea9d4e863fd2116530bf2c87c1ab40b39f9d1/asyncmy-0.2.10-cp311-cp311-win32.whl", hash = "sha256:a16633032be020b931acfd7cd1862c7dad42a96ea0b9b28786f2ec48e0a86757", size = 1622375, upload-time = "2024-12-13T02:36:22.276Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a3/76e65877de5e6fc853373908079adb711f80ed09aab4e152a533e0322375/asyncmy-0.2.10-cp311-cp311-win_amd64.whl", hash = "sha256:cca06212575922216b89218abd86a75f8f7375fc9c28159ea469f860785cdbc7", size = 1696693, upload-time = "2024-12-13T02:36:26.879Z" }, + { url = "https://files.pythonhosted.org/packages/b8/82/5a4b1aedae9b35f7885f10568437d80507d7a6704b51da2fc960a20c4948/asyncmy-0.2.10-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:42295530c5f36784031f7fa42235ef8dd93a75d9b66904de087e68ff704b4f03", size = 1783558, upload-time = "2024-12-13T02:36:28.922Z" }, + { url = "https://files.pythonhosted.org/packages/39/24/0fce480680531a29b51e1d2680a540c597e1a113aa1dc58cb7483c123a6b/asyncmy-0.2.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:641a853ffcec762905cbeceeb623839c9149b854d5c3716eb9a22c2b505802af", size = 1729268, upload-time = "2024-12-13T02:36:50.423Z" }, + { url = "https://files.pythonhosted.org/packages/c8/96/74dc1aaf1ab0bde88d3c6b3a70bd25f18796adb4e91b77ad580efe232df5/asyncmy-0.2.10-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:c554874223dd36b1cfc15e2cd0090792ea3832798e8fe9e9d167557e9cf31b4d", size = 5343513, upload-time = "2024-12-13T02:36:17.099Z" }, + { url = "https://files.pythonhosted.org/packages/9a/04/14662ff5b9cfab5cc11dcf91f2316e2f80d88fbd2156e458deef3e72512a/asyncmy-0.2.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd16e84391dde8edb40c57d7db634706cbbafb75e6a01dc8b68a63f8dd9e44ca", size = 5592344, upload-time = "2024-12-13T02:36:21.202Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ac/3cf0abb3acd4f469bd012a1b4a01968bac07a142fca510da946b6ab1bf4f/asyncmy-0.2.10-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9f6b44c4bf4bb69a2a1d9d26dee302473099105ba95283b479458c448943ed3c", size = 5300819, upload-time = "2024-12-13T02:36:24.703Z" }, + { url = "https://files.pythonhosted.org/packages/5c/23/6d05254d1c89ad15e7f32eb3df277afc7bbb2220faa83a76bea0b7bc6407/asyncmy-0.2.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:16d398b1aad0550c6fe1655b6758455e3554125af8aaf1f5abdc1546078c7257", size = 5548799, upload-time = "2024-12-13T02:36:29.945Z" }, + { url = "https://files.pythonhosted.org/packages/fe/32/b7ce9782c741b6a821a0d11772f180f431a5c3ba6eaf2e6dfa1c3cbcf4df/asyncmy-0.2.10-cp312-cp312-win32.whl", hash = "sha256:59d2639dcc23939ae82b93b40a683c15a091460a3f77fa6aef1854c0a0af99cc", size = 1597544, upload-time = "2024-12-13T02:36:31.574Z" }, + { url = "https://files.pythonhosted.org/packages/94/08/7de4f4a17196c355e4706ceba0ab60627541c78011881a7c69f41c6414c5/asyncmy-0.2.10-cp312-cp312-win_amd64.whl", hash = "sha256:4c6674073be97ffb7ac7f909e803008b23e50281131fef4e30b7b2162141a574", size = 1679064, upload-time = "2024-12-13T02:36:39.479Z" }, + { url = "https://files.pythonhosted.org/packages/12/c8/eaa11a1716ce4505fa4d06d04abd8e1bda3aaa71c7d29209330dbd061b7a/asyncmy-0.2.10-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:244289bd1bea84384866bde50b09fe5b24856640e30a04073eacb71987b7b6ad", size = 1807310, upload-time = "2024-12-13T02:36:16.401Z" }, + { url = "https://files.pythonhosted.org/packages/3a/50/4137cb6f0e2e57bee6ff71c5cbabea66efb88b90abc9d409609368d8314a/asyncmy-0.2.10-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:6c9d024b160b9f869a21e62c4ef34a7b7a4b5a886ae03019d4182621ea804d2c", size = 1739290, upload-time = "2024-12-13T02:36:32.437Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/46a7315d8a927ac012806c9502fd4d0b210554b415ef4a44319f961475b6/asyncmy-0.2.10-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b57594eea942224626203503f24fa88a47eaab3f13c9f24435091ea910f4b966", size = 4967850, upload-time = "2024-12-13T02:35:35.221Z" }, + { url = "https://files.pythonhosted.org/packages/98/a2/fc991b329594bb372ddba296c89d7ace34271e35d92260cbea397abec40c/asyncmy-0.2.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:346192941470ac2d315f97afa14c0131ff846c911da14861baf8a1f8ed541664", size = 5169902, upload-time = "2024-12-13T02:35:37.81Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c6/754aaf8d28ea76cf86cef6d07489f277221dbc8e1fd38490a037a3138e58/asyncmy-0.2.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:957c2b48c5228e5f91fdf389daf38261a7b8989ad0eb0d1ba4e5680ef2a4a078", size = 5012169, upload-time = "2024-12-13T02:35:41.364Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/b66524785b89929da09adb5373ab360cc4ac2d97153dbd5b32e9904ac375/asyncmy-0.2.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:472989d7bfa405c108a7f3c408bbed52306504fb3aa28963d833cb7eeaafece0", size = 5186908, upload-time = "2024-12-13T02:35:44.398Z" }, + { url = "https://files.pythonhosted.org/packages/90/2f/36fbf0a7555507ce06bf5fa6f743d94f7bc38c1e6bfb5e9ba5dd51001b33/asyncmy-0.2.10-cp39-cp39-win32.whl", hash = "sha256:714b0fdadd72031e972de2bbbd14e35a19d5a7e001594f0c8a69f92f0d05acc9", size = 1626531, upload-time = "2024-12-13T02:36:07.777Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/cd737fbc8c1c14a0c39ca6d7e8f482c73a3990ecb150f2e7b2c5f2d665ab/asyncmy-0.2.10-cp39-cp39-win_amd64.whl", hash = "sha256:9fb58645d3da0b91db384f8519b16edc7dc421c966ada8647756318915d63696", size = 1696557, upload-time = "2024-12-13T02:36:10.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/32/3317d5290737a3c4685343fe37e02567518357c46ed87c51f47139d31ded/asyncmy-0.2.10-pp310-pypy310_pp73-macosx_13_0_x86_64.whl", hash = "sha256:f10c977c60a95bd6ec6b8654e20c8f53bad566911562a7ad7117ca94618f05d3", size = 1627680, upload-time = "2024-12-13T02:36:42.605Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e1/afeb50deb0554006c48b9f4f7b6b726e0aa42fa96d7cfbd3fdd0800765e2/asyncmy-0.2.10-pp310-pypy310_pp73-macosx_14_0_arm64.whl", hash = "sha256:aab07fbdb9466beaffef136ffabe388f0d295d8d2adb8f62c272f1d4076515b9", size = 1593957, upload-time = "2024-12-13T02:37:00.344Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/56d3721e2b2eab84320058c3458da168d143446031eca3799aed481c33d2/asyncmy-0.2.10-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:63144322ade68262201baae73ad0c8a06b98a3c6ae39d1f3f21c41cc5287066a", size = 1756531, upload-time = "2024-12-13T02:36:59.477Z" }, + { url = "https://files.pythonhosted.org/packages/ac/1a/295f06eb8e5926749265e08da9e2dc0dc14e0244bf36843997a1c8e18a50/asyncmy-0.2.10-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9659d95c6f2a611aec15bdd928950df937bf68bc4bbb68b809ee8924b6756067", size = 1752746, upload-time = "2024-12-13T02:37:01.999Z" }, + { url = "https://files.pythonhosted.org/packages/ab/09/3a5351acc6273c28333cad8193184de0070c617fd8385fd8ba23d789e08d/asyncmy-0.2.10-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8ced4bd938e95ede0fb9fa54755773df47bdb9f29f142512501e613dd95cf4a4", size = 1614903, upload-time = "2024-12-13T02:36:53Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d1/28829c381e52166563706f2bc5e8043ab8599fc1d7e9c8ab26b21f2b33f4/asyncmy-0.2.10-pp39-pypy39_pp73-macosx_13_0_x86_64.whl", hash = "sha256:4651caaee6f4d7a8eb478a0dc460f8e91ab09a2d8d32444bc2b235544c791947", size = 1625889, upload-time = "2024-12-13T02:36:40.475Z" }, + { url = "https://files.pythonhosted.org/packages/00/7f/110e9ef7cb38ff599725bbed08b76f656b2eae7505971ebc2a78b20716b9/asyncmy-0.2.10-pp39-pypy39_pp73-macosx_14_0_arm64.whl", hash = "sha256:ac091b327f01c38d91c697c810ba49e5f836890d48f6879ba0738040bb244290", size = 1592247, upload-time = "2024-12-13T02:36:58.319Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/036d5c23193f2c24b8dd4610eeae70380034d9ef37c29785c1624a19c92f/asyncmy-0.2.10-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:e1d2d9387cd3971297486c21098e035c620149c9033369491f58fe4fc08825b6", size = 1754251, upload-time = "2024-12-13T02:36:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/f97378316a48168e380948c814b346038f0f72fd99c986c42cba493edc7e/asyncmy-0.2.10-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a760cb486ddb2c936711325236e6b9213564a9bb5deb2f6949dbd16c8e4d739e", size = 1751010, upload-time = "2024-12-13T02:36:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/24/7b/3f90c33daab8409498a6e57760c6bd23ba3ecef3c684b59c9c6177030073/asyncmy-0.2.10-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1586f26633c05b16bcfc46d86e9875f4941280e12afa79a741cdf77ae4ccfb4d", size = 1613533, upload-time = "2024-12-13T02:36:48.203Z" }, +] + +[[package]] +name = "asyncodbc" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyodbc" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/0b/22492b37913e1e85806cf760fad88f261df1492a96677f8c99e9af57d9bb/asyncodbc-0.1.1.tar.gz", hash = "sha256:feb0d4bb35e028d37d914b1ddcbd576400be6058bf256bc226793c966f846d54", size = 17238, upload-time = "2022-05-20T04:44:43.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/6f/d9759613546f57611bde94d4fb96d494d2132f937b0de2acc9eb42edcb4e/asyncodbc-0.1.1-py3-none-any.whl", hash = "sha256:ce1ba96b4b5b6b37d6c2134c435172c91f05c15c7ce1ec830be2806b6e545442", size = 22856, upload-time = "2022-05-20T04:44:42.316Z" }, +] + +[[package]] +name = "asyncpg" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/07/1650a8c30e3a5c625478fa8aafd89a8dd7d85999bf7169b16f54973ebf2c/asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e", size = 673143, upload-time = "2024-10-20T00:29:08.846Z" }, + { url = "https://files.pythonhosted.org/packages/a0/9a/568ff9b590d0954553c56806766914c149609b828c426c5118d4869111d3/asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0", size = 645035, upload-time = "2024-10-20T00:29:12.02Z" }, + { url = "https://files.pythonhosted.org/packages/de/11/6f2fa6c902f341ca10403743701ea952bca896fc5b07cc1f4705d2bb0593/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f", size = 2912384, upload-time = "2024-10-20T00:29:13.644Z" }, + { url = "https://files.pythonhosted.org/packages/83/83/44bd393919c504ffe4a82d0aed8ea0e55eb1571a1dea6a4922b723f0a03b/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af", size = 2947526, upload-time = "2024-10-20T00:29:15.871Z" }, + { url = "https://files.pythonhosted.org/packages/08/85/e23dd3a2b55536eb0ded80c457b0693352262dc70426ef4d4a6fc994fa51/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75", size = 2895390, upload-time = "2024-10-20T00:29:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/9b/26/fa96c8f4877d47dc6c1864fef5500b446522365da3d3d0ee89a5cce71a3f/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f", size = 3015630, upload-time = "2024-10-20T00:29:21.186Z" }, + { url = "https://files.pythonhosted.org/packages/34/00/814514eb9287614188a5179a8b6e588a3611ca47d41937af0f3a844b1b4b/asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf", size = 568760, upload-time = "2024-10-20T00:29:22.769Z" }, + { url = "https://files.pythonhosted.org/packages/f0/28/869a7a279400f8b06dd237266fdd7220bc5f7c975348fea5d1e6909588e9/asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50", size = 625764, upload-time = "2024-10-20T00:29:25.882Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0e/f5d708add0d0b97446c402db7e8dd4c4183c13edaabe8a8500b411e7b495/asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a", size = 674506, upload-time = "2024-10-20T00:29:27.988Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a0/67ec9a75cb24a1d99f97b8437c8d56da40e6f6bd23b04e2f4ea5d5ad82ac/asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed", size = 645922, upload-time = "2024-10-20T00:29:29.391Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d9/a7584f24174bd86ff1053b14bb841f9e714380c672f61c906eb01d8ec433/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a", size = 3079565, upload-time = "2024-10-20T00:29:30.832Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/a4c0f9660e333114bdb04d1a9ac70db690dd4ae003f34f691139a5cbdae3/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956", size = 3109962, upload-time = "2024-10-20T00:29:33.114Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/199fd16b5a981b1575923cbb5d9cf916fdc936b377e0423099f209e7e73d/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056", size = 3064791, upload-time = "2024-10-20T00:29:34.677Z" }, + { url = "https://files.pythonhosted.org/packages/77/52/0004809b3427534a0c9139c08c87b515f1c77a8376a50ae29f001e53962f/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454", size = 3188696, upload-time = "2024-10-20T00:29:36.389Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/fbad941cd466117be58b774a3f1cc9ecc659af625f028b163b1e646a55fe/asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d", size = 567358, upload-time = "2024-10-20T00:29:37.915Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0a/0a32307cf166d50e1ad120d9b81a33a948a1a5463ebfa5a96cc5606c0863/asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f", size = 629375, upload-time = "2024-10-20T00:29:39.987Z" }, + { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162, upload-time = "2024-10-20T00:29:41.88Z" }, + { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025, upload-time = "2024-10-20T00:29:43.352Z" }, + { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243, upload-time = "2024-10-20T00:29:44.922Z" }, + { url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059, upload-time = "2024-10-20T00:29:46.891Z" }, + { url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596, upload-time = "2024-10-20T00:29:49.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632, upload-time = "2024-10-20T00:29:50.768Z" }, + { url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186, upload-time = "2024-10-20T00:29:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064, upload-time = "2024-10-20T00:29:53.757Z" }, + { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373, upload-time = "2024-10-20T00:29:55.165Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745, upload-time = "2024-10-20T00:29:57.14Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103, upload-time = "2024-10-20T00:29:58.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471, upload-time = "2024-10-20T00:30:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253, upload-time = "2024-10-20T00:30:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720, upload-time = "2024-10-20T00:30:04.501Z" }, + { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404, upload-time = "2024-10-20T00:30:06.537Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623, upload-time = "2024-10-20T00:30:09.024Z" }, + { url = "https://files.pythonhosted.org/packages/b4/82/d94f3ed6921136a0ef40a825740eda19437ccdad7d92d924302dca1d5c9e/asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad", size = 673026, upload-time = "2024-10-20T00:30:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/4e/db/7db8b73c5d86ec9a21807f405e0698f8f637a8a3ca14b7b6fd4259b66bcf/asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff", size = 644732, upload-time = "2024-10-20T00:30:28.393Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a0/1f1910659d08050cb3e8f7d82b32983974798d7fd4ddf7620b8e2023d4ac/asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708", size = 2911761, upload-time = "2024-10-20T00:30:30.569Z" }, + { url = "https://files.pythonhosted.org/packages/4d/53/5aa0d92488ded50bab2b6626430ed9743b0b7e2d864a2b435af1ccbf219a/asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144", size = 2946595, upload-time = "2024-10-20T00:30:32.244Z" }, + { url = "https://files.pythonhosted.org/packages/c5/cd/d6d548d8ee721f4e0f7fbbe509bbac140d556c2e45814d945540c96cf7d4/asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb", size = 2890135, upload-time = "2024-10-20T00:30:33.817Z" }, + { url = "https://files.pythonhosted.org/packages/46/f0/28df398b685dabee20235e24880e1f6486d84ae7e6b0d11bdebc17740e7a/asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547", size = 3011889, upload-time = "2024-10-20T00:30:35.378Z" }, + { url = "https://files.pythonhosted.org/packages/c8/07/8c7ffe6fe8bccff9b12fcb6410b1b2fa74b917fd8b837806a40217d5228b/asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a", size = 569406, upload-time = "2024-10-20T00:30:37.644Z" }, + { url = "https://files.pythonhosted.org/packages/05/51/f59e4df6d9b8937530d4b9fdee1598b93db40c631fe94ff3ce64207b7a95/asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773", size = 626581, upload-time = "2024-10-20T00:30:39.69Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "ciso8601" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/e9/d83711081c997540aee59ad2f49d81f01d33e8551d766b0ebde346f605af/ciso8601-2.3.2.tar.gz", hash = "sha256:ec1616969aa46c51310b196022e5d3926f8d3fa52b80ec17f6b4133623bd5434", size = 28214, upload-time = "2024-12-09T12:26:40.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/54/7522b0056ff0f59790d15cc043fdbf067d9af0fa313e4a8811b65c0b4ded/ciso8601-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1bb2d4d20d7ed65fcc7137652d7d980c6eb2aa19c935579309170137d33064ce", size = 15724, upload-time = "2024-12-09T12:25:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/a4/98/17f6e3bf01857cc2594f29ecc5316a4591d40e54c14d85cde433fc0d5cbb/ciso8601-2.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:3039f11ced0bc971341ab63be222860eb2cc942d51a7aa101b1809b633ad2288", size = 23809, upload-time = "2024-12-09T12:25:48.043Z" }, + { url = "https://files.pythonhosted.org/packages/9d/48/6a396459dd24b3d7a05b3858b58c3dbc19a7d561cd9accaf77b6ddd2242c/ciso8601-2.3.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:d64634b02cfb194e54569d8de3ace89cec745644cab38157aea0b03d32031eda", size = 15586, upload-time = "2024-12-09T12:25:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/64/4f9362e7f3817d9a408e17ae0a71cd3d16037e33e4dd0f3a301b4727c2e5/ciso8601-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0dcb8dc5998bc50346cec9d3b8b5deda8ddabeda70a923c110efb5100cd9754", size = 39279, upload-time = "2024-12-09T12:25:50.91Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f9/56a707dc73604472dfdab611f210c22741b73b5b7795b6dc6422d072bae6/ciso8601-2.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a3ca99eadbee4a9bb7dfb2bcf266a21828033853cd99803a9893d3473ac0e9", size = 39014, upload-time = "2024-12-09T12:25:52.477Z" }, + { url = "https://files.pythonhosted.org/packages/87/27/02dc32c3ff7ca18a0f595a13d5f5e749a1270a030ce8f50d6b78ae95a984/ciso8601-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d61daee5e8daee87eba34151b9952ec8c3327ad9e54686b6247dcb9b2b135312", size = 39892, upload-time = "2024-12-09T12:25:54.182Z" }, + { url = "https://files.pythonhosted.org/packages/41/ba/57016baecaab3f9fae9399ee0ad25573eec82042c55560f86d004717381c/ciso8601-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2f20654de6b0374eade96d8dcb0642196632067b6dd2e24068c563ac6b8551c6", size = 39650, upload-time = "2024-12-09T12:25:55.803Z" }, + { url = "https://files.pythonhosted.org/packages/9a/00/489c0724d2a273384344b76c1420f21ede894d3f1d9ba240176f0d8595e6/ciso8601-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0e856903cb6019ab26849af7270ef183b2314f87fd17686a8c98315eff794df", size = 15722, upload-time = "2024-12-09T12:25:57.851Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5e/5c29a477ec5207f0e1604fbd15280401e4715163bf51f316b5ee907da1c4/ciso8601-2.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d99297a5925ef3c9ac316cab082c1b1623d976acdb5056fbb8cb12a854116351", size = 23815, upload-time = "2024-12-09T12:25:59.477Z" }, + { url = "https://files.pythonhosted.org/packages/bf/35/1000cebcd41863394ec3d4ba05656be9a20ae4a27de1646da12c6d336cdd/ciso8601-2.3.2-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:2e740d2dcac81b5adb0cff641706d5a9e54ff4f3bb7e24437cdacdab3937c0a3", size = 15585, upload-time = "2024-12-09T12:26:00.356Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/f763d2bfa22a50fb004d77106c18a58dbde3fa5d4c5cf7d096bb23af8dc5/ciso8601-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e883a08b294694313bd3a85c1a136f4326ca26050552742c489159c52e296060", size = 39534, upload-time = "2024-12-09T12:26:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ba/012ac7082fd10c15c0cd347cb62ad88eaf135dc6e4b6190a9becf9acfeaa/ciso8601-2.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6994b393b1e1147dbc2f13d6d508f6e95b96d7f770299a4af70b7c1d380242c1", size = 39260, upload-time = "2024-12-09T12:26:02.975Z" }, + { url = "https://files.pythonhosted.org/packages/02/41/8310f8998c3e98c334f8dfaf905725c85771eee4ece9e5ee833070d483f2/ciso8601-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d31a04bea97f21b797fd414b465c00283b70d9523e8e51bc303bec04195a278", size = 40014, upload-time = "2024-12-09T12:26:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/00/9f/28d592b034a8a8c1ddeac55812172d9b22942077e681c84c268173bfe4e1/ciso8601-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce014a3559592320a2a7a7205257e57dd1277580038a30f153627c5d30ed7a07", size = 39861, upload-time = "2024-12-09T12:26:05.082Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fc/e852e664bb90bf1112e17778512d6cbc5fa5f49b7c22969e4ee131f13d06/ciso8601-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:75870a1e496a17e9e8d2ac90125600e1bafe51679d2836b2f6cb66908fef7ad6", size = 15755, upload-time = "2024-12-09T12:26:07.259Z" }, + { url = "https://files.pythonhosted.org/packages/22/da/c82e665c627836be4d7d0a8ed38518f9833124a6fd85735881cac72427b8/ciso8601-2.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:c117c415c43aa3db68ee16a2446cb85c5e88459650421d773f6f6444ce5e5819", size = 24291, upload-time = "2024-12-09T12:26:08.23Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6a/822b178b6c473533e5023aab6447b05d1683f95c3210eda5680f9262c93c/ciso8601-2.3.2-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:ce5f76297b6138dc5c085d4c5a0a631afded99f250233fe583dc365f67fe8a8d", size = 15713, upload-time = "2024-12-09T12:26:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/de/c3/63b89c7ec2a4f9bcbdeb3401485992d13eeb4da943accef58f0820c62552/ciso8601-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e3205e4cfd63100f454ea67100c7c6123af32da0022bdc6e81058e95476a8ad", size = 40253, upload-time = "2024-12-09T12:26:10.149Z" }, + { url = "https://files.pythonhosted.org/packages/96/01/b12f356afaa6dfc339c4b964f01c7b78f7d844dfe087cbbc9c68a5f048c0/ciso8601-2.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5308a14ac72898f91332ccfded2f18a6c558ccd184ccff84c4fb36c7e4c2a0e6", size = 40087, upload-time = "2024-12-09T12:26:11.066Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/de5f920ebf5cdb2ef28237bdb48ac9ea980d794e16f1fbedffc430064208/ciso8601-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e825cb5ecd232775a94ef3c456ab19752ee8e66eaeb20562ea45472eaa8614ec", size = 40908, upload-time = "2024-12-09T12:26:12.092Z" }, + { url = "https://files.pythonhosted.org/packages/b2/3c/cd79c9305480cc9bf8dce286bd7ec2035a3d140b3f3ae0b1232087a65240/ciso8601-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a8f96f91bdeabee7ebca2c6e48185bea45e195f406ff748c87a3c9ecefb25cc", size = 40881, upload-time = "2024-12-09T12:26:12.997Z" }, + { url = "https://files.pythonhosted.org/packages/a3/bb/ba7bb497fa6093e478e54d870c8ffbfcd44c6b6400a3a0c8f485b229595b/ciso8601-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fbbe659093d4aef1e66de0ee9a10487439527be4b2f6a6710960f98a41e2cc5", size = 15716, upload-time = "2024-12-09T12:26:24.576Z" }, + { url = "https://files.pythonhosted.org/packages/af/8a/f08d6c7bd5f53f2896e91ffdb849e1197fb543a011ee2f7c3200ffe1c1e8/ciso8601-2.3.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:8ccb16db7ca83cc39df3c73285e9ab4920a90f0dbef566f60f0c6cca44becaba", size = 23807, upload-time = "2024-12-09T12:26:25.635Z" }, + { url = "https://files.pythonhosted.org/packages/53/6b/6d3f90c4018eec00aa560356cf41fd1315d084863305c077f6af484ce673/ciso8601-2.3.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:dac06a1bd3c12ab699c29024c5f052e7016cb904e085a5e2b26e6b92fd2dd1dc", size = 15586, upload-time = "2024-12-09T12:26:26.583Z" }, + { url = "https://files.pythonhosted.org/packages/1a/78/078b57970e82b2dc80ce89249beb4c0d464bffb9e84d479dcd8179c36d0f/ciso8601-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a323aa0143ad8e99d7a0b0ac3005419c505e073e6f850f0443b5994b31a52d14", size = 38927, upload-time = "2024-12-09T12:26:28.16Z" }, + { url = "https://files.pythonhosted.org/packages/78/b9/0fc865c2fd9870b6d220f2f9f47fd2ab51af29ebd8ceae56e7059bc43841/ciso8601-2.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e9290e7e1b1c3a6df3967e3f1b22c334c980e841f5a1967ab6ef92b30a540d8", size = 38633, upload-time = "2024-12-09T12:26:29.186Z" }, + { url = "https://files.pythonhosted.org/packages/20/30/d4303931e31fe2df4b2c886cbac261b9e69515cade3d1de639793fba5092/ciso8601-2.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc2a6bb31030b875c7706554b99e1d724250e0fc8160aa2f3ae32520b8dccbc5", size = 39488, upload-time = "2024-12-09T12:26:30.202Z" }, + { url = "https://files.pythonhosted.org/packages/99/28/5a2d1e7df04a37ad5e19c149ba74eb1f1fdbcef48ad13acd9d71d1ec9454/ciso8601-2.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e137cf862c724a9477b62d89fb8190f141ed6d036f6c4cf824be6d9a7b819e", size = 39244, upload-time = "2024-12-09T12:26:31.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/344f1db4606408ac00803692baf988fdd8d4c82abaf9911272286dc30785/ciso8601-2.3.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc2f7090e7b8427288b9528fa9571682426f2c7d45d39cf940321192d8796c8", size = 17483, upload-time = "2024-12-09T12:26:34.183Z" }, + { url = "https://files.pythonhosted.org/packages/99/76/572d904b3307d9ab01b4586a0935b0a62dded5e57565ac87cd13a55dd332/ciso8601-2.3.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c585a05d745c36f974030d1831ed899f8b00afd760f6eff6b8de7eef72cb1336", size = 16559, upload-time = "2024-12-09T12:26:35.051Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c4/49c6e651cd8310580dc930d1f430ef35d7827e0335f2ff2c2c4f5da308b1/ciso8601-2.3.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff397592a0eadd5e0aec395a285751707c655439abb874ad93e34d04d925ec8d", size = 17480, upload-time = "2024-12-09T12:26:38.241Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f5/2e9fdb94d2c7efe6389b454f99136ccf358755d4290c1f4411f464f0a770/ciso8601-2.3.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7eb6c8756806f4b8320fe57e3b048dafc54e99af7586160ff9318f35fc521268", size = 16557, upload-time = "2024-12-09T12:26:39.838Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/76/17780846fc7aade1e66712e1e27dd28faa0a5d987a1f433610974959eaa8/coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055", size = 820754, upload-time = "2025-08-04T00:35:17.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/5f/5ce748ab3f142593698aff5f8a0cf020775aa4e24b9d8748b5a56b64d3f8/coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65", size = 215003, upload-time = "2025-08-04T00:33:02.977Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ed/507088561217b000109552139802fa99c33c16ad19999c687b601b3790d0/coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8", size = 215391, upload-time = "2025-08-04T00:33:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/79/1b/0f496259fe137c4c5e1e8eaff496fb95af88b71700f5e57725a4ddbe742b/coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f", size = 242367, upload-time = "2025-08-04T00:33:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8e/5a8835fb0122a2e2a108bf3527931693c4625fdc4d953950a480b9625852/coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7", size = 243627, upload-time = "2025-08-04T00:33:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/c3/96/6a528429c2e0e8d85261764d0cd42e51a429510509bcc14676ee5d1bb212/coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947", size = 245485, upload-time = "2025-08-04T00:33:10.29Z" }, + { url = "https://files.pythonhosted.org/packages/bf/82/1fba935c4d02c33275aca319deabf1f22c0f95f2c0000bf7c5f276d6f7b4/coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9", size = 243429, upload-time = "2025-08-04T00:33:11.909Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a8/c8dc0a57a729fc93be33ab78f187a8f52d455fa8f79bfb379fe23b45868d/coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b", size = 242104, upload-time = "2025-08-04T00:33:13.467Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6f/0b7da1682e2557caeed299a00897b42afde99a241a01eba0197eb982b90f/coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8", size = 242397, upload-time = "2025-08-04T00:33:14.682Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e4/54dc833dadccd519c04a28852f39a37e522bad35d70cfe038817cdb8f168/coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87", size = 217502, upload-time = "2025-08-04T00:33:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e7/2f78159c4c127549172f427dff15b02176329327bf6a6a1fcf1f603b5456/coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f", size = 218388, upload-time = "2025-08-04T00:33:17.4Z" }, + { url = "https://files.pythonhosted.org/packages/6e/53/0125a6fc0af4f2687b4e08b0fb332cd0d5e60f3ca849e7456f995d022656/coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27", size = 215119, upload-time = "2025-08-04T00:33:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2e/960d9871de9152dbc9ff950913c6a6e9cf2eb4cc80d5bc8f93029f9f2f9f/coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322", size = 215511, upload-time = "2025-08-04T00:33:20.32Z" }, + { url = "https://files.pythonhosted.org/packages/3f/34/68509e44995b9cad806d81b76c22bc5181f3535bca7cd9c15791bfd8951e/coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7", size = 245513, upload-time = "2025-08-04T00:33:21.896Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d4/9b12f357413248ce40804b0f58030b55a25b28a5c02db95fb0aa50c5d62c/coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468", size = 247350, upload-time = "2025-08-04T00:33:23.917Z" }, + { url = "https://files.pythonhosted.org/packages/b6/40/257945eda1f72098e4a3c350b1d68fdc5d7d032684a0aeb6c2391153ecf4/coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288", size = 249516, upload-time = "2025-08-04T00:33:25.5Z" }, + { url = "https://files.pythonhosted.org/packages/ff/55/8987f852ece378cecbf39a367f3f7ec53351e39a9151b130af3a3045b83f/coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406", size = 247241, upload-time = "2025-08-04T00:33:26.767Z" }, + { url = "https://files.pythonhosted.org/packages/df/ae/da397de7a42a18cea6062ed9c3b72c50b39e0b9e7b2893d7172d3333a9a1/coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9", size = 245274, upload-time = "2025-08-04T00:33:28.494Z" }, + { url = "https://files.pythonhosted.org/packages/4e/64/7baa895eb55ec0e1ec35b988687ecd5d4475ababb0d7ae5ca3874dd90ee7/coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1", size = 245882, upload-time = "2025-08-04T00:33:30.048Z" }, + { url = "https://files.pythonhosted.org/packages/24/6c/1fd76a0bd09ae75220ae9775a8290416d726f0e5ba26ea72346747161240/coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce", size = 217541, upload-time = "2025-08-04T00:33:31.376Z" }, + { url = "https://files.pythonhosted.org/packages/5f/2d/8c18fb7a6e74c79fd4661e82535bc8c68aee12f46c204eabf910b097ccc9/coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2", size = 218426, upload-time = "2025-08-04T00:33:32.976Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/425bb35e4ff7c7af177edf5dffd4154bc2a677b27696afe6526d75c77fec/coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293", size = 217116, upload-time = "2025-08-04T00:33:34.302Z" }, + { url = "https://files.pythonhosted.org/packages/4e/1e/2c752bdbbf6f1199c59b1a10557fbb6fb3dc96b3c0077b30bd41a5922c1f/coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83", size = 215311, upload-time = "2025-08-04T00:33:35.524Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/84277d73a2cafb96e24be81b7169372ba7ff28768ebbf98e55c85a491b0f/coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c", size = 215550, upload-time = "2025-08-04T00:33:37.109Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e7/5358b73b46ac76f56cc2de921eeabd44fabd0b7ff82ea4f6b8c159c4d5dc/coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518", size = 246564, upload-time = "2025-08-04T00:33:38.33Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0e/b0c901dd411cb7fc0cfcb28ef0dc6f3049030f616bfe9fc4143aecd95901/coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21", size = 248993, upload-time = "2025-08-04T00:33:39.555Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4e/a876db272072a9e0df93f311e187ccdd5f39a190c6d1c1f0b6e255a0d08e/coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0", size = 250454, upload-time = "2025-08-04T00:33:41.023Z" }, + { url = "https://files.pythonhosted.org/packages/64/d6/1222dc69f8dd1be208d55708a9f4a450ad582bf4fa05320617fea1eaa6d8/coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75", size = 248365, upload-time = "2025-08-04T00:33:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/62/e3/40fd71151064fc315c922dd9a35e15b30616f00146db1d6a0b590553a75a/coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01", size = 246562, upload-time = "2025-08-04T00:33:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/fc/14/8aa93ddcd6623ddaef5d8966268ac9545b145bce4fe7b1738fd1c3f0d957/coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b", size = 247772, upload-time = "2025-08-04T00:33:45.068Z" }, + { url = "https://files.pythonhosted.org/packages/07/4e/dcb1c01490623c61e2f2ea85cb185fa6a524265bb70eeb897d3c193efeb9/coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340", size = 217710, upload-time = "2025-08-04T00:33:46.378Z" }, + { url = "https://files.pythonhosted.org/packages/79/16/e8aab4162b5f80ad2e5e1f54b1826e2053aa2f4db508b864af647f00c239/coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388", size = 218499, upload-time = "2025-08-04T00:33:48.048Z" }, + { url = "https://files.pythonhosted.org/packages/06/7f/c112ec766e8f1131ce8ce26254be028772757b2d1e63e4f6a4b0ad9a526c/coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20", size = 217154, upload-time = "2025-08-04T00:33:49.299Z" }, + { url = "https://files.pythonhosted.org/packages/8d/04/9b7a741557f93c0ed791b854d27aa8d9fe0b0ce7bb7c52ca1b0f2619cb74/coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186", size = 215337, upload-time = "2025-08-04T00:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/02/a4/8d1088cd644750c94bc305d3cf56082b4cdf7fb854a25abb23359e74892f/coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226", size = 215596, upload-time = "2025-08-04T00:33:52.33Z" }, + { url = "https://files.pythonhosted.org/packages/01/2f/643a8d73343f70e162d8177a3972b76e306b96239026bc0c12cfde4f7c7a/coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba", size = 246145, upload-time = "2025-08-04T00:33:53.641Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4a/722098d1848db4072cda71b69ede1e55730d9063bf868375264d0d302bc9/coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074", size = 248492, upload-time = "2025-08-04T00:33:55.366Z" }, + { url = "https://files.pythonhosted.org/packages/3f/b0/8a6d7f326f6e3e6ed398cde27f9055e860a1e858317001835c521673fb60/coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57", size = 249927, upload-time = "2025-08-04T00:33:57.042Z" }, + { url = "https://files.pythonhosted.org/packages/bb/21/1aaadd3197b54d1e61794475379ecd0f68d8fc5c2ebd352964dc6f698a3d/coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb", size = 248138, upload-time = "2025-08-04T00:33:58.329Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/be75bafb2bdd22fd8bf9bf63cd5873b91bb26ec0d68f02d4b8b09c02decb/coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0", size = 246111, upload-time = "2025-08-04T00:33:59.899Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/a4f0c5e249c3cc60e6c6f30d8368e372f2d380eda40e0434c192ac27ccf5/coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a", size = 247493, upload-time = "2025-08-04T00:34:01.619Z" }, + { url = "https://files.pythonhosted.org/packages/85/99/f09b9493e44a75cf99ca834394c12f8cb70da6c1711ee296534f97b52729/coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b", size = 217756, upload-time = "2025-08-04T00:34:03.277Z" }, + { url = "https://files.pythonhosted.org/packages/2d/bb/cbcb09103be330c7d26ff0ab05c4a8861dd2e254656fdbd3eb7600af4336/coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe", size = 218526, upload-time = "2025-08-04T00:34:04.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/8f/8bfb4e0bca52c00ab680767c0dd8cfd928a2a72d69897d9b2d5d8b5f63f5/coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7", size = 217176, upload-time = "2025-08-04T00:34:05.973Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/d458ba0bf16a8204a88d74dbb7ec5520f29937ffcbbc12371f931c11efd2/coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e", size = 216058, upload-time = "2025-08-04T00:34:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1c/af4dfd2d7244dc7610fed6d59d57a23ea165681cd764445dc58d71ed01a6/coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03", size = 216273, upload-time = "2025-08-04T00:34:09.073Z" }, + { url = "https://files.pythonhosted.org/packages/8e/67/ec5095d4035c6e16368226fa9cb15f77f891194c7e3725aeefd08e7a3e5a/coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0", size = 257513, upload-time = "2025-08-04T00:34:10.403Z" }, + { url = "https://files.pythonhosted.org/packages/1c/47/be5550b57a3a8ba797de4236b0fd31031f88397b2afc84ab3c2d4cf265f6/coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0", size = 259377, upload-time = "2025-08-04T00:34:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/37/50/b12a4da1382e672305c2d17cd3029dc16b8a0470de2191dbf26b91431378/coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1", size = 261516, upload-time = "2025-08-04T00:34:13.608Z" }, + { url = "https://files.pythonhosted.org/packages/db/41/4d3296dbd33dd8da178171540ca3391af7c0184c0870fd4d4574ac290290/coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1", size = 259110, upload-time = "2025-08-04T00:34:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/b409959ecbc0cec0e61e65683b22bacaa4a3b11512f834e16dd8ffbc37db/coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca", size = 257248, upload-time = "2025-08-04T00:34:16.501Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/7076dc1c240412e9267d36ec93e9e299d7659f6a5c1e958f87e998b0fb6d/coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb", size = 258063, upload-time = "2025-08-04T00:34:18.338Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/f6b51a0288f8f5f7dcc7c89abdd22cf514f3bc5151284f5cd628917f8e10/coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824", size = 218433, upload-time = "2025-08-04T00:34:19.71Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6d/547a86493e25270ce8481543e77f3a0aa3aa872c1374246b7b76273d66eb/coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3", size = 219523, upload-time = "2025-08-04T00:34:21.171Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/3c711e38eaf9ab587edc9bed232c0298aed84e751a9f54aaa556ceaf7da6/coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f", size = 217739, upload-time = "2025-08-04T00:34:22.514Z" }, + { url = "https://files.pythonhosted.org/packages/71/53/83bafa669bb9d06d4c8c6a055d8d05677216f9480c4698fb183ba7ec5e47/coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6", size = 215328, upload-time = "2025-08-04T00:34:23.991Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6c/30827a9c5a48a813e865fbaf91e2db25cce990bd223a022650ef2293fe11/coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b", size = 215608, upload-time = "2025-08-04T00:34:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a0/c92d85948056ddc397b72a3d79d36d9579c53cb25393ed3c40db7d33b193/coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be", size = 246111, upload-time = "2025-08-04T00:34:26.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/d695cf86b2559aadd072c91720a7844be4fb82cb4a3b642a2c6ce075692d/coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1", size = 248419, upload-time = "2025-08-04T00:34:28.726Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0a/03206aec4a05986e039418c038470d874045f6e00426b0c3879adc1f9251/coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95", size = 250038, upload-time = "2025-08-04T00:34:30.061Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9b/b3bd6bd52118c12bc4cf319f5baba65009c9beea84e665b6b9f03fa3f180/coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46", size = 248066, upload-time = "2025-08-04T00:34:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/bfa92e261d3e055c851a073e87ba6a3bff12a1f7134233e48a8f7d855875/coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303", size = 245909, upload-time = "2025-08-04T00:34:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/12/80/c8df15db4847710c72084164f615ae900af1ec380dce7f74a5678ccdf5e1/coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd", size = 247329, upload-time = "2025-08-04T00:34:34.388Z" }, + { url = "https://files.pythonhosted.org/packages/04/6f/cb66e1f7124d5dd9ced69f889f02931419cb448125e44a89a13f4e036124/coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8", size = 218007, upload-time = "2025-08-04T00:34:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e1/3d4be307278ce32c1b9d95cc02ee60d54ddab784036101d053ec9e4fe7f5/coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3", size = 218802, upload-time = "2025-08-04T00:34:37.35Z" }, + { url = "https://files.pythonhosted.org/packages/ec/66/1e43bbeb66c55a5a5efec70f1c153cf90cfc7f1662ab4ebe2d844de9122c/coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc", size = 217397, upload-time = "2025-08-04T00:34:39.15Z" }, + { url = "https://files.pythonhosted.org/packages/81/01/ae29c129217f6110dc694a217475b8aecbb1b075d8073401f868c825fa99/coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b", size = 216068, upload-time = "2025-08-04T00:34:40.648Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/6e9221d4139f357258f36dfa1d8cac4ec56d9d5acf5fdcc909bb016954d7/coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4", size = 216285, upload-time = "2025-08-04T00:34:42.441Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ec/89d1d0c0ece0d296b4588e0ef4df185200456d42a47f1141335f482c2fc5/coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b", size = 257603, upload-time = "2025-08-04T00:34:43.899Z" }, + { url = "https://files.pythonhosted.org/packages/82/06/c830af66734671c778fc49d35b58339e8f0687fbd2ae285c3f96c94da092/coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de", size = 259568, upload-time = "2025-08-04T00:34:45.519Z" }, + { url = "https://files.pythonhosted.org/packages/60/57/f280dd6f1c556ecc744fbf39e835c33d3ae987d040d64d61c6f821e87829/coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca", size = 261691, upload-time = "2025-08-04T00:34:47.019Z" }, + { url = "https://files.pythonhosted.org/packages/54/2b/c63a0acbd19d99ec32326164c23df3a4e18984fb86e902afdd66ff7b3d83/coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8", size = 259166, upload-time = "2025-08-04T00:34:48.792Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c5/cd2997dcfcbf0683634da9df52d3967bc1f1741c1475dd0e4722012ba9ef/coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4", size = 257241, upload-time = "2025-08-04T00:34:51.038Z" }, + { url = "https://files.pythonhosted.org/packages/16/26/c9e30f82fdad8d47aee90af4978b18c88fa74369ae0f0ba0dbf08cee3a80/coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed", size = 258139, upload-time = "2025-08-04T00:34:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/c9/99/bdb7bd00bebcd3dedfb895fa9af8e46b91422993e4a37ac634a5f1113790/coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0", size = 218809, upload-time = "2025-08-04T00:34:54.075Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5e/56a7852e38a04d1520dda4dfbfbf74a3d6dec932c20526968f7444763567/coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf", size = 219926, upload-time = "2025-08-04T00:34:55.643Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/7fbe6b9c52bb9d627e9556f9f2edfdbe88b315e084cdecc9afead0c3b36a/coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc", size = 217925, upload-time = "2025-08-04T00:34:57.564Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/139fa9f64edfa5bae1492a4efecef7209f59ba5f9d862db594be7a85d7fb/coverage-7.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:765b13b164685a2f8b2abef867ad07aebedc0e090c757958a186f64e39d63dbd", size = 215003, upload-time = "2025-08-04T00:34:59.079Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9f/8682ccdd223c2ab34de6575ef3c78fae9bdaece1710b4d95bb9b0abd4d2f/coverage-7.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a219b70100500d0c7fd3ebb824a3302efb6b1a122baa9d4eb3f43df8f0b3d899", size = 215382, upload-time = "2025-08-04T00:35:00.772Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4e/45b9658499db7149e1ed5b46ccac6101dc5c0ddb786a0304f7bb0c0d90d4/coverage-7.10.2-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e33e79a219105aa315439ee051bd50b6caa705dc4164a5aba6932c8ac3ce2d98", size = 241457, upload-time = "2025-08-04T00:35:02.696Z" }, + { url = "https://files.pythonhosted.org/packages/dd/66/aaf159bfe94ee3996b8786034a8e713bc68cd650aa7c1a41b612846cdc41/coverage-7.10.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc3945b7bad33957a9eca16e9e5eae4b17cb03173ef594fdaad228f4fc7da53b", size = 243354, upload-time = "2025-08-04T00:35:04.238Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/8fd2f67d8580380e7b19b23838e308b6757197e94a1b3b87e0ad483f70c8/coverage-7.10.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bdff88e858ee608a924acfad32a180d2bf6e13e059d6a7174abbae075f30436", size = 244923, upload-time = "2025-08-04T00:35:06.159Z" }, + { url = "https://files.pythonhosted.org/packages/55/90/67b129b08200e08962961f56604083923bc8484bc641c92ee6801c1ae822/coverage-7.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44329cbed24966c0b49acb386352c9722219af1f0c80db7f218af7793d251902", size = 242856, upload-time = "2025-08-04T00:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/4d/8f/3f428363f713ab3432e602665cdefe436fd427263471644dd3742b6eebd8/coverage-7.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:be127f292496d0fbe20d8025f73221b36117b3587f890346e80a13b310712982", size = 241092, upload-time = "2025-08-04T00:35:09.381Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e8531ea19f047b8b1d1d1c85794e4b35ae762e570f072ca2afbce67be176/coverage-7.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c031da749a05f7a01447dd7f47beedb498edd293e31e1878c0d52db18787df0", size = 242044, upload-time = "2025-08-04T00:35:10.929Z" }, + { url = "https://files.pythonhosted.org/packages/62/6b/22cb6281b4d06b73edae2facc7935a15151ddb8e8d8928a184b7a3100289/coverage-7.10.2-cp39-cp39-win32.whl", hash = "sha256:22aca3e691c7709c5999ccf48b7a8ff5cf5a8bd6fe9b36efbd4993f5a36b2fcf", size = 217512, upload-time = "2025-08-04T00:35:12.801Z" }, + { url = "https://files.pythonhosted.org/packages/9e/83/bce22e6880837de640d6ff630c7493709a3511f93c5154a326b337f01a81/coverage-7.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c7195444b932356055a8e287fa910bf9753a84a1bc33aeb3770e8fca521e032e", size = 218406, upload-time = "2025-08-04T00:35:14.351Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/9b768ac73a8ac2d10c080af23937212434a958c8d2a1c84e89b450237942/coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f", size = 206973, upload-time = "2025-08-04T00:35:15.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "dmpython" +version = "2.5.22" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/6d/c70cf7b8d15a14a5e5363dfc71793ad7a2371cfe5ae2ef0bcd27909a126c/dmPython-2.5.22-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:53eed0a9c2893fc0d9e6996d04ddda5e6f0c9a9439bddadd82c578763a922af6", size = 5517130, upload-time = "2025-05-27T06:22:22.969Z" }, + { url = "https://files.pythonhosted.org/packages/70/8d/38b1bcf49978b5d895604dd1582e88cbbc0c7f3ae43b9d6610148ed208aa/dmPython-2.5.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e53b389418b8589c563c0e30a1396a53b5758f6c362428dc7acf78e2cb03cdc", size = 6231612, upload-time = "2025-05-28T02:49:50.777Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/d7757bc9d91a7f30e6c993fface3e3380c974e653b4e2cc5c8c436a65487/dmPython-2.5.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80053cce65edd3e56db0b25dde6845ea5f9d6ebe66e9baab13d1e55a91bca46c", size = 5517129, upload-time = "2025-05-27T06:22:31.557Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f1/3014e8bdfe5467a9125af47f1db0c858991acd1c5696e52b6fd5c857065b/dmPython-2.5.22-cp310-cp310-win_amd64.whl", hash = "sha256:952257b49a497ba9981f30e2438b68b9591a837b9694a99bc6ee5bdf352577b1", size = 4871274, upload-time = "2025-05-27T06:21:05.187Z" }, + { url = "https://files.pythonhosted.org/packages/0e/48/b541b2078e1a52029db547c1d097489ebce34a3d20c20e343b060346ca3b/dmPython-2.5.22-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d08946ac19369b8e3a20cdf6f4f76fcd983056a10c99203e0b5c2510921687f", size = 5517129, upload-time = "2025-05-27T06:22:40.424Z" }, + { url = "https://files.pythonhosted.org/packages/a8/fd/fc021e2187c9c32c2e44f81e44871e9190ad5c206d9c1044ef06f1d71553/dmPython-2.5.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06be246c88c3c50d982dcb7a2e67df697426a22ea1d659d1d843a7b7c4c6a932", size = 6515515, upload-time = "2025-05-28T02:50:01.298Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8a/2af65843685ff1ac2999668e450cfc49f1edcdabb16305b41f9e2d62bf61/dmPython-2.5.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80582dc673d64ee887470263c7c13497bd2ce9b6c3c8ea01b3c08373f34eacfe", size = 5517128, upload-time = "2025-05-27T06:22:49.193Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2c/a95f19db327dd720466341b7c3350ee00208e46b46290df60901508ae204/dmPython-2.5.22-cp311-cp311-win_amd64.whl", hash = "sha256:dc735e9f26a58d3a6b44a80e60cc1001d450df00351fcdb15f83949e2390ea25", size = 4871273, upload-time = "2025-05-27T06:21:13.467Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ea/c7f114debc5d63b29c1861bef9aff38b67c9eef8e5e7906c28cd46650921/dmPython-2.5.22-cp312-cp312-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:298623504d13a8090341765b77f7d6a5d666bc8e1783e75b5c874df5434f6402", size = 5189904, upload-time = "2025-05-27T06:22:57.472Z" }, + { url = "https://files.pythonhosted.org/packages/18/e9/6afd6cd64e72cfb9aaaa1ab082358ca9318994e15414a82abe5ef1b42864/dmPython-2.5.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:756767239710f582f2570e788b399ac1d327a809f18254e0849097d48978b03a", size = 6248941, upload-time = "2025-05-28T02:50:11.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/bdbaab95c28dc50e855c75d1cef19aead000d29a2ebd047addb05321b132/dmPython-2.5.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03e75c6bb0d2588c5055a8a7aa86fd613fd2c2124346a96d1646e754626ea427", size = 5189904, upload-time = "2025-05-27T06:23:05.692Z" }, + { url = "https://files.pythonhosted.org/packages/22/18/284f0896e6f8503af1c32373a6856b341488313b17817a2ceff217e00897/dmPython-2.5.22-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f10e2be68315ed809cf21ca8eb41e94e8202889f28c7546ba16d124af0dd4493", size = 5146870, upload-time = "2025-05-27T06:24:04.251Z" }, + { url = "https://files.pythonhosted.org/packages/de/7d/cea3612f5ee5b0067d5f5de415aa8775c0735b6c1ef5ea87aa5ecc9f78f2/dmPython-2.5.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d205c41eb77ad2dbfc7c472f4983de887d80bb28b8e9c9d836fe6e6963fb701e", size = 6229854, upload-time = "2025-05-28T02:50:51.666Z" }, + { url = "https://files.pythonhosted.org/packages/bb/4f/56d4158d2f98c15552eec5c9f0d7e8b69c33d1d95585fddacf02cdb22299/dmPython-2.5.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a19f6ec5f1404aeeb1fa577c619207ee61d682dfae5e32c0da8e76815eaa2014", size = 5146870, upload-time = "2025-05-27T06:24:12.27Z" }, + { url = "https://files.pythonhosted.org/packages/0f/db/adac2287c573f7b7021f02e4e448fef8467ce7748c4773c7f70a4843369a/dmpython-2.5.22-cp312-cp312-win_amd64.whl", hash = "sha256:9fc9303a2a25bd007d150ad9910f7e14adffdaca0255dbff713810aef3681338", size = 4808663, upload-time = "2025-05-27T06:21:21.084Z" }, + { url = "https://files.pythonhosted.org/packages/28/86/b0e69ae36ba684652dc4f3c72e131a98b088f507ac8ac4b3d7b7f3113274/dmpython-2.5.22-cp313-cp313-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:27a153b81d99048eb8d52643c707a59a1427548e88a4610b68af884f7ad4d189", size = 5180226, upload-time = "2025-05-27T06:23:14.167Z" }, + { url = "https://files.pythonhosted.org/packages/19/dc/e67ac910e9d9da5f186c6ac0e50fbd8f116880cceba4893af5aef5f13886/dmpython-2.5.22-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca73651706762dd61515243532096d68ca3d3d334762a5252ee5a69bd7aa2ff", size = 6247676, upload-time = "2025-05-28T02:50:21.894Z" }, + { url = "https://files.pythonhosted.org/packages/62/db/5c1acf3338a5f42a62c6b7da309fc9e3e0b3d8d987c22cd84b4bc09c01d7/dmpython-2.5.22-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38fa1bc6b5dc1486e30e4e04a11318bd0a7ec6b7a46f81ae6cdebf2430cbbc20", size = 5180227, upload-time = "2025-05-27T06:23:22.438Z" }, + { url = "https://files.pythonhosted.org/packages/8f/40/22860ec1d8dc1e7c6bae233c220fa7409bceb0243d09f22e7fcf8ab498ec/dmpython-2.5.22-cp313-cp313-win_amd64.whl", hash = "sha256:d0dd8586aec8f23a8eda201cea546eec4e4a79c020f0f1424827f7dbb68e0c94", size = 4808630, upload-time = "2025-05-27T06:21:28.996Z" }, + { url = "https://files.pythonhosted.org/packages/21/c9/c962221750ad0d73066460e51b42a763ae9042dbd2b023e627b9b5b1664c/dmpython-2.5.22-cp39-cp39-win_amd64.whl", hash = "sha256:33042c8103004bf6f0eaf0993a4c2642eab424d62efbd3ce16f839f972336eae", size = 4808356, upload-time = "2025-05-27T06:21:52.694Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iso8601" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/f3/ef59cee614d5e0accf6fd0cbba025b93b272e626ca89fb70a3e9187c5d15/iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df", size = 6522, upload-time = "2023-10-03T00:25:39.317Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/0c/f37b6a241f0759b7653ffa7213889d89ad49a2b76eb2ddf3b57b2738c347/iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242", size = 7545, upload-time = "2023-10-03T00:25:32.304Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/3b/fd9ff8ff64ae3900f11554d5cfc835fb73e501e043c420ad32ec574fe27f/orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96", size = 5393373, upload-time = "2025-07-25T14:33:52.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/8b/7dd88f416e2e5834fd9809d871f471aae7d12dfd83d4786166fa5a926601/orjson-3.11.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:92d771c492b64119456afb50f2dff3e03a2db8b5af0eba32c5932d306f970532", size = 241312, upload-time = "2025-07-25T14:31:52.841Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5d/5bfc371bd010ffbec90e64338aa59abcb13ed94191112199048653ee2f34/orjson-3.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0085ef83a4141c2ed23bfec5fecbfdb1e95dd42fc8e8c76057bdeeec1608ea65", size = 132791, upload-time = "2025-07-25T14:31:55.547Z" }, + { url = "https://files.pythonhosted.org/packages/48/e2/c07854a6bad71e4249345efadb686c0aff250073bdab8ba9be7626af6516/orjson-3.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5caf7f13f2e1b4e137060aed892d4541d07dabc3f29e6d891e2383c7ed483440", size = 128690, upload-time = "2025-07-25T14:31:56.708Z" }, + { url = "https://files.pythonhosted.org/packages/48/e4/2e075348e7772aa1404d51d8df25ff4d6ee3daf682732cb21308e3b59c32/orjson-3.11.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f716bcc166524eddfcf9f13f8209ac19a7f27b05cf591e883419079d98c8c99d", size = 130646, upload-time = "2025-07-25T14:31:58.165Z" }, + { url = "https://files.pythonhosted.org/packages/97/09/50daacd3ac7ae564186924c8d1121940f2c78c64d6804dbe81dd735ab087/orjson-3.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507d6012fab05465d8bf21f5d7f4635ba4b6d60132874e349beff12fb51af7fe", size = 132620, upload-time = "2025-07-25T14:31:59.226Z" }, + { url = "https://files.pythonhosted.org/packages/da/21/5f22093fa90e6d6fcf8111942b530a4ad19ee1cc0b06ddad4a63b16ab852/orjson-3.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1545083b0931f754c80fd2422a73d83bea7a6d1b6de104a5f2c8dd3d64c291e", size = 135121, upload-time = "2025-07-25T14:32:00.653Z" }, + { url = "https://files.pythonhosted.org/packages/48/90/77ad4bfa6bd400a3d241695e3e39975e32fe027aea5cb0b171bd2080c427/orjson-3.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e217ce3bad76351e1eb29ebe5ca630326f45cd2141f62620107a229909501a3", size = 131131, upload-time = "2025-07-25T14:32:01.821Z" }, + { url = "https://files.pythonhosted.org/packages/5a/64/d383675229f7ffd971b6ec6cdd3016b00877bb6b2d5fc1fd099c2ec2ad57/orjson-3.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06ef26e009304bda4df42e4afe518994cde6f89b4b04c0ff24021064f83f4fbb", size = 131025, upload-time = "2025-07-25T14:32:02.879Z" }, + { url = "https://files.pythonhosted.org/packages/d4/82/e4017d8d98597f6056afaf75021ff390154d1e2722c66ba45a4d50f82606/orjson-3.11.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ba49683b87bea3ae1489a88e766e767d4f423a669a61270b6d6a7ead1c33bd65", size = 404464, upload-time = "2025-07-25T14:32:04.384Z" }, + { url = "https://files.pythonhosted.org/packages/77/7e/45c7f813c30d386c0168a32ce703494262458af6b222a3eeac1c0bb88822/orjson-3.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5072488fcc5cbcda2ece966d248e43ea1d222e19dd4c56d3f82747777f24d864", size = 146416, upload-time = "2025-07-25T14:32:05.57Z" }, + { url = "https://files.pythonhosted.org/packages/41/71/6ccb4d7875ec3349409960769a28349f477856f05de9fd961454c2b99230/orjson-3.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f58ae2bcd119226fe4aa934b5880fe57b8e97b69e51d5d91c88a89477a307016", size = 135497, upload-time = "2025-07-25T14:32:06.704Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ce/df8dac7da075962fdbfca55d53e3601aa910c9f23606033bf0f084835720/orjson-3.11.1-cp310-cp310-win32.whl", hash = "sha256:6723be919c07906781b9c63cc52dc7d2fb101336c99dd7e85d3531d73fb493f7", size = 136807, upload-time = "2025-07-25T14:32:08.303Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a0/f6c2be24709d1742d878b4530fa0c3f4a5e190d51397b680abbf44d11dbf/orjson-3.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:5fd44d69ddfdfb4e8d0d83f09d27a4db34930fba153fbf79f8d4ae8b47914e04", size = 131561, upload-time = "2025-07-25T14:32:09.444Z" }, + { url = "https://files.pythonhosted.org/packages/a5/92/7ab270b5b3df8d5b0d3e572ddf2f03c9f6a79726338badf1ec8594e1469d/orjson-3.11.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:15e2a57ce3b57c1a36acffcc02e823afefceee0a532180c2568c62213c98e3ef", size = 240918, upload-time = "2025-07-25T14:32:11.021Z" }, + { url = "https://files.pythonhosted.org/packages/80/41/df44684cfbd2e2e03bf9b09fdb14b7abcfff267998790b6acfb69ad435f0/orjson-3.11.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:17040a83ecaa130474af05bbb59a13cfeb2157d76385556041f945da936b1afd", size = 129386, upload-time = "2025-07-25T14:32:12.361Z" }, + { url = "https://files.pythonhosted.org/packages/c1/08/958f56edd18ba1827ad0c74b2b41a7ae0864718adee8ccb5d1a5528f8761/orjson-3.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a68f23f09e5626cc0867a96cf618f68b91acb4753d33a80bf16111fd7f9928c", size = 132508, upload-time = "2025-07-25T14:32:13.917Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/5e56e189dacbf51e53ba8150c20e61ee746f6d57b697f5c52315ffc88a83/orjson-3.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47e07528bb6ccbd6e32a55e330979048b59bfc5518b47c89bc7ab9e3de15174a", size = 128501, upload-time = "2025-07-25T14:32:15.13Z" }, + { url = "https://files.pythonhosted.org/packages/fe/de/f6c301a514f5934405fd4b8f3d3efc758c911d06c3de3f4be1e30d675fa4/orjson-3.11.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3807cce72bf40a9d251d689cbec28d2efd27e0f6673709f948f971afd52cb09", size = 130465, upload-time = "2025-07-25T14:32:17.355Z" }, + { url = "https://files.pythonhosted.org/packages/47/08/f7dbaab87d6f05eebff2d7b8e6a8ed5f13b2fe3e3ae49472b527d03dbd7a/orjson-3.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b2dc7e88da4ca201c940f5e6127998d9e89aa64264292334dad62854bc7fc27", size = 132416, upload-time = "2025-07-25T14:32:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/43/3f/dd5a185273b7ba6aa238cfc67bf9edaa1885ae51ce942bc1a71d0f99f574/orjson-3.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3091dad33ac9e67c0a550cfff8ad5be156e2614d6f5d2a9247df0627751a1495", size = 134924, upload-time = "2025-07-25T14:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/729d23510eaa81f0ce9d938d99d72dcf5e4ed3609d9d0bcf9c8a282cc41a/orjson-3.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed0fce2307843b79a0c83de49f65b86197f1e2310de07af9db2a1a77a61ce4c", size = 130938, upload-time = "2025-07-25T14:32:21.769Z" }, + { url = "https://files.pythonhosted.org/packages/82/96/120feb6807f9e1f4c68fc842a0f227db8575eafb1a41b2537567b91c19d8/orjson-3.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a31e84782a18c30abd56774c0cfa7b9884589f4d37d9acabfa0504dad59bb9d", size = 130811, upload-time = "2025-07-25T14:32:22.931Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/4695e946a453fa22ff945da4b1ed0691b3f4ec86b828d398288db4a0ff79/orjson-3.11.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26b6c821abf1ae515fbb8e140a2406c9f9004f3e52acb780b3dee9bfffddbd84", size = 404272, upload-time = "2025-07-25T14:32:25.238Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7b/1c953e2c9e55af126c6cb678a30796deb46d7713abdeb706b8765929464c/orjson-3.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f857b3d134b36a8436f1e24dcb525b6b945108b30746c1b0b556200b5cb76d39", size = 146196, upload-time = "2025-07-25T14:32:26.909Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c2/bef5d3bc83f2e178592ff317e2cf7bd38ebc16b641f076ea49f27aadd1d3/orjson-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df146f2a14116ce80f7da669785fcb411406d8e80136558b0ecda4c924b9ac55", size = 135336, upload-time = "2025-07-25T14:32:28.22Z" }, + { url = "https://files.pythonhosted.org/packages/92/95/bc6006881ebdb4608ed900a763c3e3c6be0d24c3aadd62beb774f9464ec6/orjson-3.11.1-cp311-cp311-win32.whl", hash = "sha256:d777c57c1f86855fe5492b973f1012be776e0398571f7cc3970e9a58ecf4dc17", size = 136665, upload-time = "2025-07-25T14:32:29.976Z" }, + { url = "https://files.pythonhosted.org/packages/59/c3/1f2b9cc0c60ea2473d386fed2df2b25ece50aeb73c798d4669aadff3061e/orjson-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9a5fd589951f02ec2fcb8d69339258bbf74b41b104c556e6d4420ea5e059313", size = 131388, upload-time = "2025-07-25T14:32:31.595Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e5/40c97e5a6b85944022fe54b463470045b8651b7bb2f1e16a95c42812bf97/orjson-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:4cddbe41ee04fddad35d75b9cf3e3736ad0b80588280766156b94783167777af", size = 126786, upload-time = "2025-07-25T14:32:32.787Z" }, + { url = "https://files.pythonhosted.org/packages/98/77/e55513826b712807caadb2b733eee192c1df105c6bbf0d965c253b72f124/orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910", size = 240955, upload-time = "2025-07-25T14:32:34.056Z" }, + { url = "https://files.pythonhosted.org/packages/c9/88/a78132dddcc9c3b80a9fa050b3516bb2c996a9d78ca6fb47c8da2a80a696/orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d", size = 129294, upload-time = "2025-07-25T14:32:35.323Z" }, + { url = "https://files.pythonhosted.org/packages/09/02/6591e0dcb2af6bceea96cb1b5f4b48c1445492a3ef2891ac4aa306bb6f73/orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5", size = 132310, upload-time = "2025-07-25T14:32:36.53Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/c1cfbc617bcfa4835db275d5e0fe9bbdbe561a4b53d3b2de16540ec29c50/orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b", size = 128529, upload-time = "2025-07-25T14:32:37.817Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bd/91a156c5df3aaf1d68b2ab5be06f1969955a8d3e328d7794f4338ac1d017/orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf", size = 130925, upload-time = "2025-07-25T14:32:39.03Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4c/a65cc24e9a5f87c9833a50161ab97b5edbec98bec99dfbba13827549debc/orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56", size = 132432, upload-time = "2025-07-25T14:32:40.619Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4d/3fc3e5d7115f4f7d01b481e29e5a79bcbcc45711a2723242787455424f40/orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289", size = 135069, upload-time = "2025-07-25T14:32:41.84Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c6/7585aa8522af896060dc0cd7c336ba6c574ae854416811ee6642c505cc95/orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81", size = 131045, upload-time = "2025-07-25T14:32:43.085Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4e/b8a0a943793d2708ebc39e743c943251e08ee0f3279c880aefd8e9cb0c70/orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968", size = 130597, upload-time = "2025-07-25T14:32:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/72/2b/7d30e2aed2f585d5d385fb45c71d9b16ba09be58c04e8767ae6edc6c9282/orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a", size = 404207, upload-time = "2025-07-25T14:32:45.612Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7e/772369ec66fcbce79477f0891918309594cd00e39b67a68d4c445d2ab754/orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d", size = 146628, upload-time = "2025-07-25T14:32:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c8/62bdb59229d7e393ae309cef41e32cc1f0b567b21dfd0742da70efb8b40c/orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4", size = 135449, upload-time = "2025-07-25T14:32:48.727Z" }, + { url = "https://files.pythonhosted.org/packages/02/47/1c99aa60e19f781424eabeaacd9e999eafe5b59c81ead4273b773f0f3af1/orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf", size = 136653, upload-time = "2025-07-25T14:32:50.622Z" }, + { url = "https://files.pythonhosted.org/packages/31/9a/132999929a2892ab07e916669accecc83e5bff17e11a1186b4c6f23231f0/orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae", size = 131426, upload-time = "2025-07-25T14:32:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/9c/77/d984ee5a1ca341090902e080b187721ba5d1573a8d9759e0c540975acfb2/orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d", size = 126635, upload-time = "2025-07-25T14:32:53.2Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e9/880ef869e6f66279ce3a381a32afa0f34e29a94250146911eee029e56efc/orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e", size = 240835, upload-time = "2025-07-25T14:32:54.507Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1f/52039ef3d03eeea21763b46bc99ebe11d9de8510c72b7b5569433084a17e/orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064", size = 129226, upload-time = "2025-07-25T14:32:55.908Z" }, + { url = "https://files.pythonhosted.org/packages/ee/da/59fdffc9465a760be2cd3764ef9cd5535eec8f095419f972fddb123b6d0e/orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6", size = 132261, upload-time = "2025-07-25T14:32:57.538Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5c/8610911c7e969db7cf928c8baac4b2f1e68d314bc3057acf5ca64f758435/orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894", size = 128614, upload-time = "2025-07-25T14:32:58.808Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a1/a1db9d4310d014c90f3b7e9b72c6fb162cba82c5f46d0b345669eaebdd3a/orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912", size = 130968, upload-time = "2025-07-25T14:33:00.038Z" }, + { url = "https://files.pythonhosted.org/packages/56/ff/11acd1fd7c38ea7a1b5d6bf582ae3da05931bee64620995eb08fd63c77fe/orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2", size = 132439, upload-time = "2025-07-25T14:33:01.354Z" }, + { url = "https://files.pythonhosted.org/packages/70/f9/bb564dd9450bf8725e034a8ad7f4ae9d4710a34caf63b85ce1c0c6d40af0/orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8", size = 135299, upload-time = "2025-07-25T14:33:03.079Z" }, + { url = "https://files.pythonhosted.org/packages/94/bb/c8eafe6051405e241dda3691db4d9132d3c3462d1d10a17f50837dd130b4/orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4", size = 131004, upload-time = "2025-07-25T14:33:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/a2/40/bed8d7dcf1bd2df8813bf010a25f645863a2f75e8e0ebdb2b55784cf1a62/orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24", size = 130583, upload-time = "2025-07-25T14:33:05.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/e7/cfa2eb803ad52d74fbb5424a429b5be164e51d23f1d853e5e037173a5c48/orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014", size = 404218, upload-time = "2025-07-25T14:33:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/bc703af5bc6e9c7e18dcf4404dcc4ec305ab9bb6c82d3aee5952c0c56abf/orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058", size = 146605, upload-time = "2025-07-25T14:33:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d26a0150534c4965a06f556aa68bf3c3b82999d5d7b0facd3af7b390c4af/orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f", size = 135434, upload-time = "2025-07-25T14:33:09.967Z" }, + { url = "https://files.pythonhosted.org/packages/89/b6/1cb28365f08cbcffc464f8512320c6eb6db6a653f03d66de47ea3c19385f/orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092", size = 136596, upload-time = "2025-07-25T14:33:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/f9/35/7870d0d3ed843652676d84d8a6038791113eacc85237b673b925802826b8/orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77", size = 131319, upload-time = "2025-07-25T14:33:12.614Z" }, + { url = "https://files.pythonhosted.org/packages/b7/3e/5bcd50fd865eb664d4edfdaaaff51e333593ceb5695a22c0d0a0d2b187ba/orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4", size = 126613, upload-time = "2025-07-25T14:33:13.927Z" }, + { url = "https://files.pythonhosted.org/packages/61/d8/0a5cd31ed100b4e569e143cb0cddefc21f0bcb8ce284f44bca0bb0e10f3d/orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b", size = 240819, upload-time = "2025-07-25T14:33:15.223Z" }, + { url = "https://files.pythonhosted.org/packages/b9/95/7eb2c76c92192ceca16bc81845ff100bbb93f568b4b94d914b6a4da47d61/orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e", size = 129218, upload-time = "2025-07-25T14:33:16.637Z" }, + { url = "https://files.pythonhosted.org/packages/da/84/e6b67f301b18adbbc346882f456bea44daebbd032ba725dbd7b741e3a7f1/orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727", size = 132238, upload-time = "2025-07-25T14:33:17.934Z" }, + { url = "https://files.pythonhosted.org/packages/84/78/a45a86e29d9b2f391f9d00b22da51bc4b46b86b788fd42df2c5fcf3e8005/orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c", size = 130998, upload-time = "2025-07-25T14:33:19.282Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8f/6eb3ee6760d93b2ce996a8529164ee1f5bafbdf64b74c7314b68db622b32/orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037", size = 130559, upload-time = "2025-07-25T14:33:20.589Z" }, + { url = "https://files.pythonhosted.org/packages/1b/78/9572ae94bdba6813917c9387e7834224c011ea6b4530ade07d718fd31598/orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa", size = 404231, upload-time = "2025-07-25T14:33:22.019Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a3/68381ad0757e084927c5ee6cfdeab1c6c89405949ee493db557e60871c4c/orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de", size = 146658, upload-time = "2025-07-25T14:33:23.675Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/fac56acf77aab778296c3f541a3eec643266f28ecd71d6c0cba251e47655/orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45", size = 135443, upload-time = "2025-07-25T14:33:25.04Z" }, + { url = "https://files.pythonhosted.org/packages/76/b1/326fa4b87426197ead61c1eec2eeb3babc9eb33b480ac1f93894e40c8c08/orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1", size = 136643, upload-time = "2025-07-25T14:33:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8e/2987ae2109f3bfd39680f8a187d1bc09ad7f8fb019dcdc719b08c7242ade/orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8", size = 131324, upload-time = "2025-07-25T14:33:27.896Z" }, + { url = "https://files.pythonhosted.org/packages/21/5f/253e08e6974752b124fbf3a4de3ad53baa766b0cb4a333d47706d307e396/orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1", size = 126605, upload-time = "2025-07-25T14:33:29.244Z" }, + { url = "https://files.pythonhosted.org/packages/f5/64/ce5c07420fe7367bd3da769161f07ae54b35c552468c6eb7947c023a25c6/orjson-3.11.1-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3d593a9e0bccf2c7401ae53625b519a7ad7aa555b1c82c0042b322762dc8af4e", size = 241861, upload-time = "2025-07-25T14:33:30.585Z" }, + { url = "https://files.pythonhosted.org/packages/94/17/7894ff2867e83d0d5cdda6e41210963a88764b292ec7a91fa93bcb5afd9e/orjson-3.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baad413c498fc1eef568504f11ea46bc71f94b845c075e437da1e2b85b4fb86", size = 132485, upload-time = "2025-07-25T14:33:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/8e/38/e8f907733e281e65ba912be552fe5ad5b53f0fdddaa0b43c3a9bc0bce5df/orjson-3.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22cf17ae1dae3f9b5f37bfcdba002ed22c98bbdb70306e42dc18d8cc9b50399a", size = 128513, upload-time = "2025-07-25T14:33:33.571Z" }, + { url = "https://files.pythonhosted.org/packages/d5/49/d6d0f23036a16c9909ca4cb09d53b2bf9341e7b1ae7d03ded302a3673448/orjson-3.11.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e855c1e97208133ce88b3ef6663c9a82ddf1d09390cd0856a1638deee0390c3c", size = 130462, upload-time = "2025-07-25T14:33:35.061Z" }, + { url = "https://files.pythonhosted.org/packages/04/70/df75afdfe6d3c027c03d656f0a5074159ace27a24dbf22d4af7fabf811df/orjson-3.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5861c5f7acff10599132854c70ab10abf72aebf7c627ae13575e5f20b1ab8fe", size = 132438, upload-time = "2025-07-25T14:33:36.893Z" }, + { url = "https://files.pythonhosted.org/packages/56/ef/938ae6995965cc7884d8460177bed20248769d1edf99d1904dfd46eebd7d/orjson-3.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1e6415c5b5ff3a616a6dafad7b6ec303a9fc625e9313c8e1268fb1370a63dcb", size = 134928, upload-time = "2025-07-25T14:33:38.755Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/97be96e9ed22123724611c8511f306a69e6cd0273d4c6424edda5716d108/orjson-3.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912579642f5d7a4a84d93c5eed8daf0aa34e1f2d3f4dc6571a8e418703f5701e", size = 130903, upload-time = "2025-07-25T14:33:40.585Z" }, + { url = "https://files.pythonhosted.org/packages/86/ed/7cf17c1621a5a4c6716dfa8099dc9a4153cc8bd402195ae9028d7e5286e3/orjson-3.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2092e1d3b33f64e129ff8271642afddc43763c81f2c30823b4a4a4a5f2ea5b55", size = 130793, upload-time = "2025-07-25T14:33:42.397Z" }, + { url = "https://files.pythonhosted.org/packages/5e/72/add1805918b6af187c193895d38bddc7717eea30d1ea8b25833a9668b469/orjson-3.11.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:b8ac64caba1add2c04e9cd4782d4d0c4d6c554b7a3369bdec1eed7854c98db7b", size = 404283, upload-time = "2025-07-25T14:33:44.035Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f1/b27c05bab8b49ff2fb30e6c42e8602ae51d6c9dd19564031da37f7ea61ba/orjson-3.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:23196b826ebc85c43f8e27bee0ab33c5fb13a29ea47fb4fcd6ebb1e660eb0252", size = 146169, upload-time = "2025-07-25T14:33:46.036Z" }, + { url = "https://files.pythonhosted.org/packages/91/5b/5a2cdc081bc2093708726887980d8f0c7c0edc31ab0d3c5ccc1db70ede0e/orjson-3.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f2d3364cfad43003f1e3d564a069c8866237cca30f9c914b26ed2740b596ed00", size = 135304, upload-time = "2025-07-25T14:33:47.519Z" }, + { url = "https://files.pythonhosted.org/packages/01/7f/fe09ebaecbaec6a741b29f79ccbbe38736dff51e8413f334067ad914df26/orjson-3.11.1-cp39-cp39-win32.whl", hash = "sha256:20b0dca94ea4ebe4628330de50975b35817a3f52954c1efb6d5d0498a3bbe581", size = 136652, upload-time = "2025-07-25T14:33:49.38Z" }, + { url = "https://files.pythonhosted.org/packages/97/2f/71fe70d7d06087d8abef423843d880e3d4cf21cfc38c299feebb0a98f7c1/orjson-3.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:200c3ad7ed8b5d31d49143265dfebd33420c4b61934ead16833b5cd2c3d241be", size = 131373, upload-time = "2025-07-25T14:33:51.359Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "psycopg" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] +pool = [ + { name = "psycopg-pool" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/ce/d677bc51f9b180986e5515268603519cee682eb6b5e765ae46cdb8526579/psycopg_binary-3.2.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:528239bbf55728ba0eacbd20632342867590273a9bacedac7538ebff890f1093", size = 4033081, upload-time = "2025-05-13T16:06:29.666Z" }, + { url = "https://files.pythonhosted.org/packages/de/f4/b56263eb20dc36d71d7188622872098400536928edf86895736e28546b3c/psycopg_binary-3.2.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4978c01ca4c208c9d6376bd585e2c0771986b76ff7ea518f6d2b51faece75e8", size = 4082141, upload-time = "2025-05-13T16:06:33.81Z" }, + { url = "https://files.pythonhosted.org/packages/68/47/5316c3b0a2b1ff5f1d440a27638250569994534874a2ce88bf24f5c51c0f/psycopg_binary-3.2.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ed2bab85b505d13e66a914d0f8cdfa9475c16d3491cf81394e0748b77729af2", size = 4678993, upload-time = "2025-05-13T16:06:36.309Z" }, + { url = "https://files.pythonhosted.org/packages/53/24/b2c667b59f07fd7d7805c0c2074351bf2b98a336c5030d961db316512ffb/psycopg_binary-3.2.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799fa1179ab8a58d1557a95df28b492874c8f4135101b55133ec9c55fc9ae9d7", size = 4500117, upload-time = "2025-05-13T16:06:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/ae/91/a08f8878b0fe0b34b083c149df950bce168bc1b18b2fe849fa42bf4378d4/psycopg_binary-3.2.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb37ac3955d19e4996c3534abfa4f23181333974963826db9e0f00731274b695", size = 4766985, upload-time = "2025-05-13T16:06:42.502Z" }, + { url = "https://files.pythonhosted.org/packages/10/be/3a45d5b7d8f4c4332fd42465f2170b5aef4d28a7c79e79ac7e5e1dac74d7/psycopg_binary-3.2.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:001e986656f7e06c273dd4104e27f4b4e0614092e544d950c7c938d822b1a894", size = 4461990, upload-time = "2025-05-13T16:06:45.971Z" }, + { url = "https://files.pythonhosted.org/packages/03/ce/20682b9a4fc270d8dc644a0b16c1978732146c6ff0abbc48fbab2f4a70aa/psycopg_binary-3.2.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa5c80d8b4cbf23f338db88a7251cef8bb4b68e0f91cf8b6ddfa93884fdbb0c1", size = 3777947, upload-time = "2025-05-13T16:06:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/07/5c/f6d486e00bcd8709908ccdd436b2a190d390dfd61e318de4060bc6ee2a1e/psycopg_binary-3.2.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:39a127e0cf9b55bd4734a8008adf3e01d1fd1cb36339c6a9e2b2cbb6007c50ee", size = 3337502, upload-time = "2025-05-13T16:06:51.378Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a1/086508e929c0123a7f532840bb0a0c8a1ebd7e06aef3ee7fa44a3589bcdf/psycopg_binary-3.2.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fb7599e436b586e265bea956751453ad32eb98be6a6e694252f4691c31b16edb", size = 3440809, upload-time = "2025-05-13T16:06:54.552Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/3a347a0f894355a6b173fca2202eca279b6197727b24e4896cf83f4263ee/psycopg_binary-3.2.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5d2c9fe14fe42b3575a0b4e09b081713e83b762c8dc38a3771dd3265f8f110e7", size = 3497231, upload-time = "2025-05-13T16:06:58.858Z" }, + { url = "https://files.pythonhosted.org/packages/18/31/0845a385eb6f4521b398793293b5f746a101e80d5c43792990442d26bc2e/psycopg_binary-3.2.9-cp310-cp310-win_amd64.whl", hash = "sha256:7e4660fad2807612bb200de7262c88773c3483e85d981324b3c647176e41fdc8", size = 2936845, upload-time = "2025-05-13T16:07:02.712Z" }, + { url = "https://files.pythonhosted.org/packages/b6/84/259ea58aca48e03c3c793b4ccfe39ed63db7b8081ef784d039330d9eed96/psycopg_binary-3.2.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2504e9fd94eabe545d20cddcc2ff0da86ee55d76329e1ab92ecfcc6c0a8156c4", size = 4040785, upload-time = "2025-05-13T16:07:07.569Z" }, + { url = "https://files.pythonhosted.org/packages/25/22/ce58ffda2b7e36e45042b4d67f1bbd4dd2ccf4cfd2649696685c61046475/psycopg_binary-3.2.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:093a0c079dd6228a7f3c3d82b906b41964eaa062a9a8c19f45ab4984bf4e872b", size = 4087601, upload-time = "2025-05-13T16:07:11.75Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4f/b043e85268650c245025e80039b79663d8986f857bc3d3a72b1de67f3550/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:387c87b51d72442708e7a853e7e7642717e704d59571da2f3b29e748be58c78a", size = 4676524, upload-time = "2025-05-13T16:07:17.038Z" }, + { url = "https://files.pythonhosted.org/packages/da/29/7afbfbd3740ea52fda488db190ef2ef2a9ff7379b85501a2142fb9f7dd56/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9ac10a2ebe93a102a326415b330fff7512f01a9401406896e78a81d75d6eddc", size = 4495671, upload-time = "2025-05-13T16:07:21.709Z" }, + { url = "https://files.pythonhosted.org/packages/ea/eb/df69112d18a938cbb74efa1573082248437fa663ba66baf2cdba8a95a2d0/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72fdbda5b4c2a6a72320857ef503a6589f56d46821592d4377c8c8604810342b", size = 4768132, upload-time = "2025-05-13T16:07:25.818Z" }, + { url = "https://files.pythonhosted.org/packages/76/fe/4803b20220c04f508f50afee9169268553f46d6eed99640a08c8c1e76409/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f34e88940833d46108f949fdc1fcfb74d6b5ae076550cd67ab59ef47555dba95", size = 4458394, upload-time = "2025-05-13T16:07:29.148Z" }, + { url = "https://files.pythonhosted.org/packages/0f/0f/5ecc64607ef6f62b04e610b7837b1a802ca6f7cb7211339f5d166d55f1dd/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a3e0f89fe35cb03ff1646ab663dabf496477bab2a072315192dbaa6928862891", size = 3776879, upload-time = "2025-05-13T16:07:32.503Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d8/1c3d6e99b7db67946d0eac2cd15d10a79aa7b1e3222ce4aa8e7df72027f5/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6afb3e62f2a3456f2180a4eef6b03177788df7ce938036ff7f09b696d418d186", size = 3333329, upload-time = "2025-05-13T16:07:35.555Z" }, + { url = "https://files.pythonhosted.org/packages/d7/02/a4e82099816559f558ccaf2b6945097973624dc58d5d1c91eb1e54e5a8e9/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:cc19ed5c7afca3f6b298bfc35a6baa27adb2019670d15c32d0bb8f780f7d560d", size = 3435683, upload-time = "2025-05-13T16:07:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/91/e4/f27055290d58e8818bed8a297162a096ef7f8ecdf01d98772d4b02af46c4/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc75f63653ce4ec764c8f8c8b0ad9423e23021e1c34a84eb5f4ecac8538a4a4a", size = 3497124, upload-time = "2025-05-13T16:07:40.567Z" }, + { url = "https://files.pythonhosted.org/packages/67/3d/17ed07579625529534605eeaeba34f0536754a5667dbf20ea2624fc80614/psycopg_binary-3.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:3db3ba3c470801e94836ad78bf11fd5fab22e71b0c77343a1ee95d693879937a", size = 2939520, upload-time = "2025-05-13T16:07:45.467Z" }, + { url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205, upload-time = "2025-05-13T16:07:48.195Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795, upload-time = "2025-05-13T16:07:50.917Z" }, + { url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043, upload-time = "2025-05-13T16:07:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972, upload-time = "2025-05-13T16:07:57.925Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516, upload-time = "2025-05-13T16:08:01.616Z" }, + { url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160, upload-time = "2025-05-13T16:08:04.278Z" }, + { url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518, upload-time = "2025-05-13T16:08:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598, upload-time = "2025-05-13T16:08:09.999Z" }, + { url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289, upload-time = "2025-05-13T16:08:12.66Z" }, + { url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493, upload-time = "2025-05-13T16:08:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400, upload-time = "2025-05-13T16:08:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, + { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, + { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4a/e095884dd016b2bde2796043c61cd383b79e5d2a820c33e2c47293707ca8/psycopg_binary-3.2.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587a3f19954d687a14e0c8202628844db692dbf00bba0e6d006659bf1ca91cbe", size = 4034274, upload-time = "2025-05-13T16:09:43.738Z" }, + { url = "https://files.pythonhosted.org/packages/11/e9/ab3fad6033de260a620f6481e66092417ce31fa194dbf9ac292ab8cb9fd0/psycopg_binary-3.2.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:791759138380df21d356ff991265fde7fe5997b0c924a502847a9f9141e68786", size = 4083015, upload-time = "2025-05-13T16:09:54.896Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c8/6cd54a349d0b62b080761eb7bda43190003ecbbf17920d57254d5c780e11/psycopg_binary-3.2.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95315b8c8ddfa2fdcb7fe3ddea8a595c1364524f512160c604e3be368be9dd07", size = 4679369, upload-time = "2025-05-13T16:10:00.545Z" }, + { url = "https://files.pythonhosted.org/packages/51/34/35c65ac413c485e9340d62f14adcb34420acae44425f77aee591d49e6647/psycopg_binary-3.2.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18ac08475c9b971237fcc395b0a6ee4e8580bb5cf6247bc9b8461644bef5d9f4", size = 4500889, upload-time = "2025-05-13T16:10:07.593Z" }, + { url = "https://files.pythonhosted.org/packages/77/a9/f691b8037b0bcef481b09ae4283beedbf048f79b6fe9bda1445dbb14ed18/psycopg_binary-3.2.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac2c04b6345e215e65ca6aef5c05cc689a960b16674eaa1f90a8f86dfaee8c04", size = 4769218, upload-time = "2025-05-13T16:10:23.076Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/25afc811c1dfb664b31d66d6f5c070326a1f89f768f1b673273a3abe6912/psycopg_binary-3.2.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1ab25e3134774f1e476d4bb9050cdec25f10802e63e92153906ae934578734", size = 4462834, upload-time = "2025-05-13T16:10:30.442Z" }, + { url = "https://files.pythonhosted.org/packages/df/e2/eb4a8230e13f691d6e386e22b16d4b90f454839b78ac547be3f399562ee4/psycopg_binary-3.2.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4bfec4a73e8447d8fe8854886ffa78df2b1c279a7592241c2eb393d4499a17e2", size = 3779527, upload-time = "2025-05-13T16:10:42.705Z" }, + { url = "https://files.pythonhosted.org/packages/26/39/0f79c7d42f0c5711861ce9db55c65e14e7f1e52bd40304b4d6e7cd505e61/psycopg_binary-3.2.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:166acc57af5d2ff0c0c342aed02e69a0cd5ff216cae8820c1059a6f3b7cf5f78", size = 3337958, upload-time = "2025-05-13T16:10:47.874Z" }, + { url = "https://files.pythonhosted.org/packages/11/ce/28b1d98aed9337a721b271778d07c5ac7f85730d96f0185cc6d22684536d/psycopg_binary-3.2.9-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:413f9e46259fe26d99461af8e1a2b4795a4e27cc8ac6f7919ec19bcee8945074", size = 3440567, upload-time = "2025-05-13T16:10:57.821Z" }, + { url = "https://files.pythonhosted.org/packages/24/54/40a3a8175566f8c1268af0bacf5d7b26371697b6cefa87352c1df4b435e1/psycopg_binary-3.2.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:354dea21137a316b6868ee41c2ae7cce001e104760cf4eab3ec85627aed9b6cd", size = 3498637, upload-time = "2025-05-13T16:11:02.854Z" }, + { url = "https://files.pythonhosted.org/packages/63/ee/51748bc8af0ba08e7415fcbbd00b7d069c068f8c08509e8dd0dd0a066394/psycopg_binary-3.2.9-cp39-cp39-win_amd64.whl", hash = "sha256:24ddb03c1ccfe12d000d950c9aba93a7297993c4e3905d9f2c9795bb0764d523", size = 2938614, upload-time = "2025-05-13T16:11:13.299Z" }, +] + +[[package]] +name = "psycopg-pool" +version = "3.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770, upload-time = "2025-02-26T12:03:47.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252, upload-time = "2025-02-26T12:03:45.073Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" }, + { url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" }, + { url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" }, + { url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" }, + { url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" }, + { url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" }, + { url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" }, + { url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, + { url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" }, + { url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" }, + { url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" }, + { url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" }, + { url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" }, + { url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pymysql" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/ce59b5e5ed4ce8512f879ff1fa5ab699d211ae2495f1adaa5fbba2a1eada/pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0", size = 47678, upload-time = "2024-05-21T11:03:43.722Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972, upload-time = "2024-05-21T11:03:41.216Z" }, +] + +[[package]] +name = "pyodbc" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/36/a1ac7d23a1611e7ccd4d27df096f3794e8d1e7faa040260d9d41b6fc3185/pyodbc-5.2.0.tar.gz", hash = "sha256:de8be39809c8ddeeee26a4b876a6463529cd487a60d1393eb2a93e9bcd44a8f5", size = 116908, upload-time = "2024-10-16T01:40:13.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/01/05c4a4ec122c4a8a37fa1be5bdbf6fb23724a2ee3b1b771bb46f710158a9/pyodbc-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb0850e3e3782f57457feed297e220bb20c3e8fd7550d7a6b6bb96112bd9b6fe", size = 72483, upload-time = "2024-10-16T01:39:23.697Z" }, + { url = "https://files.pythonhosted.org/packages/73/22/ba718cc5508bdfbb53e1906018d7f597be37241c769dda8a48f52af96fe3/pyodbc-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0dae0fb86078c87acf135dbe5afd3c7d15d52ab0db5965c44159e84058c3e2fb", size = 71794, upload-time = "2024-10-16T01:39:25.372Z" }, + { url = "https://files.pythonhosted.org/packages/24/e4/9d859ea3642059c10a6644a00ccb1f8b8e02c1e4f49ab34250db1273c2c5/pyodbc-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6493b9c7506ca964b80ad638d0dc82869df7058255d71f04fdd1405e88bcb36b", size = 332850, upload-time = "2024-10-16T01:39:27.789Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a7/98c3555c10cfeb343ec7eea69ecb17476aa3ace72131ea8a4a1f8250318c/pyodbc-5.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04de873607fb960e71953c164c83e8e5d9291ce0d69e688e54947b254b04902", size = 336009, upload-time = "2024-10-16T01:39:29.694Z" }, + { url = "https://files.pythonhosted.org/packages/24/c1/d5b16dd62eb70f281bc90cdc1e3c46af7acda3f0f6afb34553206506ccb2/pyodbc-5.2.0-cp310-cp310-win32.whl", hash = "sha256:74135cb10c1dcdbd99fe429c61539c232140e62939fa7c69b0a373cc552e4a08", size = 62407, upload-time = "2024-10-16T01:39:31.894Z" }, + { url = "https://files.pythonhosted.org/packages/f5/12/22c83669abee4ca5915aa89172cf1673b58ca05f44dabeb8b0bac9b7fecc/pyodbc-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:d287121eeaa562b9ab3d4c52fa77c793dfedd127049273eb882a05d3d67a8ce8", size = 68874, upload-time = "2024-10-16T01:39:33.325Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a2/5907ce319a571eb1e271d6a475920edfeacd92da1021bb2a15ed1b7f6ac1/pyodbc-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4627779f0a608b51ce2d2fe6d1d395384e65ca36248bf9dbb6d7cf2c8fda1cab", size = 72536, upload-time = "2024-10-16T01:39:34.715Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b8/bd438ab2bb9481615142784b0c9778079a87ae1bca7a0fe8aabfc088aa9f/pyodbc-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d997d3b6551273647825c734158ca8a6f682df269f6b3975f2499c01577ddec", size = 71825, upload-time = "2024-10-16T01:39:36.343Z" }, + { url = "https://files.pythonhosted.org/packages/8b/82/cf71ae99b511a7f20c380ce470de233a0291fa3798afa74e0adc8fad1675/pyodbc-5.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5102007a8c78dd2fc1c1b6f6147de8cfc020f81013e4b46c33e66aaa7d1bf7b1", size = 342304, upload-time = "2024-10-16T01:39:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/43/ea/03fe042f4a390df05e753ddd21c6cab006baae1eee71ce230f6e2a883944/pyodbc-5.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e3cbc7075a46c411b531ada557c4aef13d034060a70077717124cabc1717e2d", size = 346186, upload-time = "2024-10-16T01:39:39.3Z" }, + { url = "https://files.pythonhosted.org/packages/f9/80/48178bb50990147adb72ec9e377e94517a0dfaf2f2a6e3fe477d9a33671f/pyodbc-5.2.0-cp311-cp311-win32.whl", hash = "sha256:de1ee7ec2eb326b7be5e2c4ce20d472c5ef1a6eb838d126d1d26779ff5486e49", size = 62418, upload-time = "2024-10-16T01:39:40.797Z" }, + { url = "https://files.pythonhosted.org/packages/7c/6b/f0ad7d8a535d58f35f375ffbf367c68d0ec54452a431d23b0ebee4cd44c6/pyodbc-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:113f904b9852c12f10c7a3288f5a3563ecdbbefe3ccc829074a9eb8255edcd29", size = 68871, upload-time = "2024-10-16T01:39:41.997Z" }, + { url = "https://files.pythonhosted.org/packages/26/26/104525b728fedfababd3143426b9d0008c70f0d604a3bf5d4773977d83f4/pyodbc-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be43d1ece4f2cf4d430996689d89a1a15aeb3a8da8262527e5ced5aee27e89c3", size = 73014, upload-time = "2024-10-16T01:39:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7d/bb632488b603bcd2a6753b858e8bc7dd56146dd19bd72003cc09ae6e3fc0/pyodbc-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9f7badd0055221a744d76c11440c0856fd2846ed53b6555cf8f0a8893a3e4b03", size = 72515, upload-time = "2024-10-16T01:39:44.506Z" }, + { url = "https://files.pythonhosted.org/packages/ab/38/a1b9bfe5a7062672268553c2d6ff93676173b0fb4bd583e8c4f74a0e296f/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad633c52f4f4e7691daaa2278d6e6ebb2fe4ae7709e610e22c7dd1a1d620cf8b", size = 348561, upload-time = "2024-10-16T01:39:45.986Z" }, + { url = "https://files.pythonhosted.org/packages/71/82/ddb1c41c682550116f391aa6cab2052910046a30d63014bbe6d09c4958f4/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d086a8f7a302b74c9c2e77bedf954a603b19168af900d4d3a97322e773df63", size = 353962, upload-time = "2024-10-16T01:39:47.254Z" }, + { url = "https://files.pythonhosted.org/packages/e5/29/fec0e739d0c1cab155843ed71d0717f5e1694effe3f28d397168f48bcd92/pyodbc-5.2.0-cp312-cp312-win32.whl", hash = "sha256:0e4412f8e608db2a4be5bcc75f9581f386ed6a427dbcb5eac795049ba6fc205e", size = 63050, upload-time = "2024-10-16T01:39:48.8Z" }, + { url = "https://files.pythonhosted.org/packages/21/7f/3a47e022a97b017ffb73351a1061e4401bcb5aa4fc0162d04f4e5452e4fc/pyodbc-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f5686b142759c5b2bdbeaa0692622c2ebb1f10780eb3c174b85f5607fbcf55", size = 69485, upload-time = "2024-10-16T01:39:49.732Z" }, + { url = "https://files.pythonhosted.org/packages/90/be/e5f8022ec57a7ea6aa3717a3f307a44c3b012fce7ad6ec91aad3e2a56978/pyodbc-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:26844d780045bbc3514d5c2f0d89e7fda7df7db0bd24292eb6902046f5730885", size = 72982, upload-time = "2024-10-16T01:39:50.738Z" }, + { url = "https://files.pythonhosted.org/packages/5c/0e/71111e4f53936b0b99731d9b6acfc8fc95660533a1421447a63d6e519112/pyodbc-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26d2d8fd53b71204c755abc53b0379df4e23fd9a40faf211e1cb87e8a32470f0", size = 72515, upload-time = "2024-10-16T01:39:51.86Z" }, + { url = "https://files.pythonhosted.org/packages/a5/09/3c06bbc1ebb9ae15f53cefe10774809b67da643883287ba1c44ba053816a/pyodbc-5.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a27996b6d27e275dfb5fe8a34087ba1cacadfd1439e636874ef675faea5149d9", size = 347470, upload-time = "2024-10-16T01:39:53.594Z" }, + { url = "https://files.pythonhosted.org/packages/a4/35/1c7efd4665e7983169d20175014f68578e0edfcbc4602b0bafcefa522c4a/pyodbc-5.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf42c4bd323b8fd01f1cd900cca2d09232155f9b8f0b9bcd0be66763588ce64", size = 353025, upload-time = "2024-10-16T01:39:55.124Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c9/736d07fa33572abdc50d858fd9e527d2c8281f3acbb90dff4999a3662edd/pyodbc-5.2.0-cp313-cp313-win32.whl", hash = "sha256:207f16b7e9bf09c591616429ebf2b47127e879aad21167ac15158910dc9bbcda", size = 63052, upload-time = "2024-10-16T01:39:56.565Z" }, + { url = "https://files.pythonhosted.org/packages/73/2a/3219c8b7fa3788fc9f27b5fc2244017223cf070e5ab370f71c519adf9120/pyodbc-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:96d3127f28c0dacf18da7ae009cd48eac532d3dcc718a334b86a3c65f6a5ef5c", size = 69486, upload-time = "2024-10-16T01:39:57.57Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1a/bec4dd9f65a7c0c1a75641119351f0f402c721bbcea3c4eb684868259467/pyodbc-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e8f4ee2c523bbe85124540ffad62a3b62ae481f012e390ef93e0602b6302e5e", size = 72440, upload-time = "2024-10-16T01:40:05.42Z" }, + { url = "https://files.pythonhosted.org/packages/df/2f/62cce82e4547dc8c4ac3174403e24ed31bdb10bb69ad30e1bb362b960877/pyodbc-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:057b8ede91b21d9f0ef58210d1ca1aad704e641ca68ac6b02f109d86b61d7402", size = 71902, upload-time = "2024-10-16T01:40:06.379Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1a/54d9595f0471c15b1de4766ec3436763aeef980740d484d629afa778c506/pyodbc-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f0ecbc7067467df95c9b8bd38fb2682c4a13a3402d77dccaddf1e145cea8cc0", size = 329596, upload-time = "2024-10-16T01:40:07.948Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3a/88bc3bb8c15aefaf98bfadd51dae2fe492486daeb04911d8cf0a6d8dd884/pyodbc-5.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b7f8324fa01c09fe4843ad8adb0b131299ef263a1fb9e63830c9cd1d5c45e4", size = 333575, upload-time = "2024-10-16T01:40:09.898Z" }, + { url = "https://files.pythonhosted.org/packages/60/75/aedf6d10f66b22302dc3f0181cbef0cc5789f2c2a658343f10ae72f51190/pyodbc-5.2.0-cp39-cp39-win32.whl", hash = "sha256:600ef6f562f609f5612ffaa8a93827249150aa3030c867937c87b24a1608967e", size = 62379, upload-time = "2024-10-16T01:40:11.412Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9c/b1e367b07904a52f22b8707979bcbda1b5a6056c46e67e0a66241d8138aa/pyodbc-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:b77556349746fb90416a48bd114cd7323f7e2559a4b263dada935f9b406ba59b", size = 68951, upload-time = "2024-10-16T01:40:12.374Z" }, +] + +[[package]] +name = "pypika" +version = "0.48.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/2c/94ed7b91db81d61d7096ac8f2d325ec562fc75e35f3baea8749c85b28784/PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378", size = 67259, upload-time = "2022-03-15T11:22:57.066Z" } + +[[package]] +name = "pypika-tortoise" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/03/c293fd42c3669e5a79d508c3ed1ca3ffa501600d93abd904a0f86d493ba0/pypika_tortoise-0.6.1.tar.gz", hash = "sha256:36ec2c88c255b9ed7ef49a6068cdeac10dafd4ddfeb828205d3afc092507fc3a", size = 39951, upload-time = "2025-06-04T14:11:53.915Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/cd/fa8124fe37a2f1e8e362e128407b26e6a651dd6190a1e53c9fe5ab550842/pypika_tortoise-0.6.1-py3-none-any.whl", hash = "sha256:da15886f37b347e71f0869f9e4ee2f9259e6bb57455b45299c6c23d7927cbb6e", size = 46593, upload-time = "2025-06-04T14:11:52.977Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "tortoise-orm" +version = "0.25.1" +source = { editable = "." } +dependencies = [ + { name = "aiosqlite" }, + { name = "dmpython" }, + { name = "iso8601", marker = "python_full_version < '4'" }, + { name = "pydantic" }, + { name = "pypika-tortoise" }, + { name = "pytz" }, +] + +[package.optional-dependencies] +accel = [ + { name = "ciso8601", marker = "implementation_name == 'cpython' and sys_platform != 'win32'" }, + { name = "orjson" }, + { name = "uvloop", marker = "implementation_name == 'cpython' and sys_platform != 'win32'" }, +] +aiomysql = [ + { name = "aiomysql" }, +] +asyncmy = [ + { name = "asyncmy", marker = "python_full_version < '4'" }, +] +asyncodbc = [ + { name = "asyncodbc", marker = "python_full_version < '4'" }, +] +asyncpg = [ + { name = "asyncpg" }, +] +dameng = [ + { name = "dmpython" }, +] +psycopg = [ + { name = "psycopg", extra = ["binary", "pool"] }, +] + +[package.dev-dependencies] +dev = [ + { name = "pypika" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiomysql", marker = "extra == 'aiomysql'" }, + { name = "aiosqlite", specifier = ">=0.16.0,<1.0.0" }, + { name = "asyncmy", marker = "python_full_version < '4' and extra == 'asyncmy'", specifier = ">=0.2.8,<1.0.0" }, + { name = "asyncodbc", marker = "python_full_version < '4' and extra == 'asyncodbc'", specifier = ">=0.1.1,<1.0.0" }, + { name = "asyncpg", marker = "extra == 'asyncpg'" }, + { name = "ciso8601", marker = "implementation_name == 'cpython' and sys_platform != 'win32' and extra == 'accel'" }, + { name = "dmpython", specifier = ">=2.5.22" }, + { name = "dmpython", marker = "extra == 'dameng'", specifier = ">=2.5.22,<3.0.0" }, + { name = "iso8601", marker = "python_full_version < '4'", specifier = ">=2.1.0,<3.0.0" }, + { name = "orjson", marker = "extra == 'accel'" }, + { name = "psycopg", extras = ["pool", "binary"], marker = "extra == 'psycopg'", specifier = ">=3.0.12,<4.0.0" }, + { name = "pydantic", specifier = ">=2.11.7" }, + { name = "pypika-tortoise", specifier = ">=0.6.1,<1.0.0" }, + { name = "pytz" }, + { name = "uvloop", marker = "implementation_name == 'cpython' and sys_platform != 'win32' and extra == 'accel'" }, +] +provides-extras = ["accel", "asyncpg", "aiomysql", "asyncmy", "psycopg", "asyncodbc", "dameng"] + +[package.metadata.requires-dev] +dev = [ + { name = "pypika", specifier = ">=0.48.9" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-asyncio", specifier = ">=1.1.0" }, + { name = "pytest-cov", specifier = ">=6.2.1" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019, upload-time = "2024-10-14T23:37:20.068Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898, upload-time = "2024-10-14T23:37:22.663Z" }, + { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735, upload-time = "2024-10-14T23:37:25.129Z" }, + { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126, upload-time = "2024-10-14T23:37:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789, upload-time = "2024-10-14T23:37:29.385Z" }, + { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523, upload-time = "2024-10-14T23:37:32.048Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, + { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload-time = "2024-10-14T23:37:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload-time = "2024-10-14T23:37:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload-time = "2024-10-14T23:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a4/646a9d0edff7cde25fc1734695d3dfcee0501140dd0e723e4df3f0a50acb/uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b", size = 1439646, upload-time = "2024-10-14T23:38:24.656Z" }, + { url = "https://files.pythonhosted.org/packages/01/2e/e128c66106af9728f86ebfeeb52af27ecd3cb09336f3e2f3e06053707a15/uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2", size = 800931, upload-time = "2024-10-14T23:38:26.087Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1a/9fbc2b1543d0df11f7aed1632f64bdf5ecc4053cf98cdc9edb91a65494f9/uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0", size = 3829660, upload-time = "2024-10-14T23:38:27.905Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c0/392e235e4100ae3b95b5c6dac77f82b529d2760942b1e7e0981e5d8e895d/uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75", size = 3827185, upload-time = "2024-10-14T23:38:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/e1/24/a5da6aba58f99aed5255eca87d58d1760853e8302d390820cc29058408e3/uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd", size = 3705833, upload-time = "2024-10-14T23:38:31.155Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5c/6ba221bb60f1e6474474102e17e38612ec7a06dc320e22b687ab563d877f/uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff", size = 3804696, upload-time = "2024-10-14T23:38:33.633Z" }, +]