├── py.typed ├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ ├── publish.yml │ └── checks.yml ├── justfile ├── CHANGELOG.md ├── src └── dj_lite │ ├── __init__.py │ ├── enums.py │ └── configurator.py ├── LICENSE ├── pyproject.toml ├── README.md ├── tests └── configurator │ └── test_sqlite_config.py └── uv.lock /py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | .DS_Store -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: adamghill 2 | 3 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | import? 'adamghill.justfile' 2 | import? '../dotfiles/just/justfile' 3 | 4 | src := "src/dj_lite" 5 | 6 | # List commands 7 | _default: 8 | just --list --unsorted --justfile {{ justfile() }} --list-heading $'Available commands:\n' 9 | 10 | # Grab default `adamghill.justfile` from GitHub 11 | fetch: 12 | curl https://raw.githubusercontent.com/adamghill/dotfiles/master/just/justfile > adamghill.justfile 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.0 4 | 5 | - Log warning when an invalid enum attribute is used. 6 | - Fix type errors. 7 | 8 | ## 1.2.0 9 | 10 | - Update readme. 11 | 12 | ## 1.1.0 13 | 14 | - Add GitHub Action publish. 15 | 16 | ## 1.0.0 17 | 18 | - Add kwargs for all default pragmas. 19 | - Allow adding additional pragmas if needed. 20 | - Substantial updates to the readme. 21 | - Add `temp_store=MEMORY` pragma by default. 22 | 23 | ## 0.1.0 24 | 25 | - Basic functionality. 26 | -------------------------------------------------------------------------------- /src/dj_lite/__init__.py: -------------------------------------------------------------------------------- 1 | from dj_lite.configurator import sqlite_config 2 | from dj_lite.enums import JournalMode, Synchronous, TempStore, TransactionMode 3 | 4 | SQLITE_INIT_COMMAND = """PRAGMA journal_mode=WAL; 5 | PRAGMA synchronous=NORMAL; 6 | PRAGMA temp_store=MEMORY; 7 | PRAGMA mmap_size=134217728; 8 | PRAGMA journal_size_limit=27103364; 9 | PRAGMA cache_size=2000; 10 | """ 11 | 12 | 13 | __all__ = [ 14 | "SQLITE_INIT_COMMAND", 15 | "JournalMode", 16 | "Synchronous", 17 | "TempStore", 18 | "TransactionMode", 19 | "sqlite_config", 20 | ] 21 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: ["published"] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | pypi-publish: 13 | name: Upload release to PyPI 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: pypi 18 | url: https://pypi.org/project/dj-lite/ 19 | 20 | permissions: 21 | id-token: write 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Install uv 27 | uses: astral-sh/setup-uv@v3 28 | with: 29 | enable-cache: true 30 | 31 | - name: Set up Python 32 | run: uv python install 3.10 33 | 34 | - name: Build 35 | run: uv build 36 | 37 | # - name: Publish package distributions to Test PyPI 38 | # uses: pypa/gh-action-pypi-publish@release/v1 39 | # with: 40 | # repository-url: https://test.pypi.org/legacy/ 41 | # skip-existing: true 42 | 43 | - name: Publish package distributions to PyPI 44 | uses: pypa/gh-action-pypi-publish@release/v1 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 adamghill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | pytest: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: 13 | - "3.10" 14 | - "3.11" 15 | - "3.12" 16 | - "3.13" 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install uv 22 | uses: astral-sh/setup-uv@v3 23 | with: 24 | enable-cache: true 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Install the package 32 | run: uv sync --all-extras --dev 33 | 34 | - name: Run pytest 35 | run: uv run pytest 36 | 37 | ruff: 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: astral-sh/ruff-action@v1 43 | 44 | mypy: 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: Install uv 51 | uses: astral-sh/setup-uv@v3 52 | with: 53 | enable-cache: true 54 | 55 | - name: Set up Python 56 | uses: actions/setup-python@v5 57 | 58 | - name: Install the package 59 | run: uv sync --all-extras --dev 60 | 61 | - name: Run mypy 62 | run: uv run mypy src/dj_lite 63 | -------------------------------------------------------------------------------- /src/dj_lite/enums.py: -------------------------------------------------------------------------------- 1 | try: 2 | from enum import StrEnum # type: ignore[attr-defined] 3 | except ImportError: 4 | from enum import Enum 5 | 6 | class StrEnum(str, Enum): # type: ignore[no-redef] 7 | def _generate_next_value_(name, *args, **kwargs): # noqa: ARG002, N805 8 | return name.lower() 9 | 10 | def __str__(self): 11 | return self.value 12 | 13 | def __repr__(self): 14 | return f"{self.__class__.__name__}.{self.name}" 15 | 16 | 17 | class TransactionMode(StrEnum): 18 | """Defines the transaction locking behavior in SQLite. 19 | 20 | Attributes: 21 | DEFERRED: No locks are acquired until the database is first accessed. 22 | IMMEDIATE: A RESERVED lock is acquired immediately. 23 | EXCLUSIVE: An EXCLUSIVE lock is acquired immediately. 24 | """ 25 | 26 | DEFERRED = "DEFERRED" 27 | IMMEDIATE = "IMMEDIATE" 28 | EXCLUSIVE = "EXCLUSIVE" 29 | 30 | 31 | class JournalMode(StrEnum): 32 | """Defines the journal mode for SQLite database. 33 | 34 | https://sqlite.org/pragma.html#pragma_journal_mode 35 | 36 | Attributes: 37 | DELETE: Default mode, uses a rollback journal. 38 | TRUNCATE: Similar to DELETE but truncates the journal file. 39 | PERSIST: Prevents the rollback journal from being deleted. 40 | MEMORY: Stores the rollback journal in RAM. 41 | WAL: Write-Ahead Logging mode. 42 | OFF: Disables the rollback journal. 43 | """ 44 | 45 | DELETE = "DELETE" 46 | TRUNCATE = "TRUNCATE" 47 | PERSIST = "PERSIST" 48 | MEMORY = "MEMORY" 49 | WAL = "WAL" 50 | OFF = "OFF" 51 | 52 | 53 | class Synchronous(StrEnum): 54 | """Defines how aggressively SQLite will sync data to disk. 55 | 56 | https://sqlite.org/pragma.html#pragma_synchronous 57 | 58 | Attributes: 59 | OFF: No syncs occur, fastest but least safe. 60 | NORMAL: Syncs occur at critical moments. 61 | FULL: Syncs after every critical operation. 62 | EXTRA: Like FULL but with additional syncs. 63 | """ 64 | 65 | OFF = "OFF" 66 | NORMAL = "NORMAL" 67 | FULL = "FULL" 68 | EXTRA = "EXTRA" 69 | 70 | 71 | class TempStore(StrEnum): 72 | """Defines how temporary files are stored. 73 | 74 | https://sqlite.org/pragma.html#pragma_temp_store 75 | 76 | Attributes: 77 | DEFAULT: Let SQLite decide the storage method. 78 | FILE: Store temporary objects in a file. 79 | MEMORY: Store temporary objects in memory. 80 | """ 81 | 82 | DEFAULT = "DEFAULT" 83 | FILE = "FILE" 84 | MEMORY = "MEMORY" 85 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "dj-lite" 3 | version = "1.3.0" 4 | description = "Configure SQLite for production use with Django." 5 | readme = "README.md" 6 | requires-python = ">=3.9.7" 7 | license = "MIT" 8 | classifiers = [ 9 | "Development Status :: 4 - Beta", 10 | "Framework :: Django", 11 | "Framework :: Django :: 5.1", 12 | "Framework :: Django :: 5.2", 13 | "Framework :: Django :: 6", 14 | "Framework :: Django :: 6.0", 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3.10", 17 | "Programming Language :: Python :: 3.11", 18 | "Programming Language :: Python :: 3.12", 19 | "Programming Language :: Python :: 3.13", 20 | "Programming Language :: Python :: 3.14", 21 | "Programming Language :: Python :: 3.15", 22 | "Programming Language :: Python :: 3.9", 23 | "Typing :: Typed", 24 | ] 25 | 26 | dependencies = [ 27 | "typeguard>=2", 28 | ] 29 | 30 | [dependency-groups] 31 | dev = [ 32 | "mypy>=1.15.0", 33 | "ruff>=0.11.8", 34 | "pytest>=8.4.1", 35 | "pytest-cov>0", 36 | ] 37 | 38 | [tool.uv] 39 | constraint-dependencies = [ 40 | "Django>=5.1", 41 | ] 42 | 43 | [project.urls] 44 | homepage = "https://github.com/adamghill/dj-lite/" 45 | repository = "https://github.com/adamghill/dj-lite.git" 46 | 47 | [tool.ruff] 48 | src = ["src"] 49 | exclude = [] 50 | target-version = "py310" 51 | line-length = 120 52 | lint.select = [ 53 | "A", 54 | "ARG", 55 | "B", 56 | "C", 57 | "DTZ", 58 | "E", 59 | "EM", 60 | "F", 61 | "FBT", 62 | "I", 63 | "ICN", 64 | "ISC", 65 | "N", 66 | "PLC", 67 | "PLE", 68 | "PLR", 69 | "PLW", 70 | "Q", 71 | "RUF", 72 | "S", 73 | "T", 74 | "TID", 75 | "UP", 76 | "W", 77 | "YTT", 78 | ] 79 | lint.ignore = [ 80 | # Allow non-abstract empty methods in abstract base classes 81 | "B027", 82 | # Allow boolean positional values in function calls, like `dict.get(... True)` 83 | "FBT003", 84 | # Ignore checks for possible passwords 85 | "S105", "S106", "S107", 86 | # Ignore complexity 87 | "C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915", 88 | # Ignore unused variables 89 | "F841", 90 | # Ignore exception strings 91 | "EM101", "EM102", 92 | ] 93 | lint.unfixable = [ 94 | # Don't touch unused imports 95 | "F401", 96 | ] 97 | 98 | [tool.ruff.lint.pydocstyle] 99 | convention = "google" 100 | 101 | [tool.ruff.lint.isort] 102 | known-first-party = ["dj_toml_settings"] 103 | 104 | [tool.ruff.lint.flake8-tidy-imports] 105 | ban-relative-imports = "all" 106 | 107 | [tool.ruff.lint.per-file-ignores] 108 | # Tests can use magic values, assertions, and relative imports 109 | "tests/**/*" = ["PLR2004", "S101", "TID252", "ARG001"] 110 | 111 | [tool.pytest.ini_options] 112 | addopts = "--quiet --failed-first -p no:warnings -m \"not slow\"" 113 | testpaths = [ 114 | "tests" 115 | ] 116 | markers = [ 117 | "slow: marks tests as slow", 118 | ] 119 | 120 | [tool.coverage.run] 121 | branch = true 122 | parallel = true 123 | 124 | [tool.coverage.report] 125 | show_missing = true 126 | skip_covered = true 127 | skip_empty = true 128 | sort = "cover" 129 | 130 | [tool.mypy] 131 | python_version = "3.10" 132 | warn_return_any = true 133 | warn_unused_configs = true 134 | ignore_missing_imports = true 135 | 136 | [build-system] 137 | requires = ["uv_build>=0.8.12,<0.9.0"] 138 | build-backend = "uv_build" 139 | -------------------------------------------------------------------------------- /src/dj_lite/configurator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | from typing import Any 4 | 5 | from typeguard import typechecked 6 | 7 | from dj_lite.enums import JournalMode, Synchronous, TempStore, TransactionMode 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def warn_on_invalid_enum(enum_cls, text: str) -> None: 13 | """Log a warning if an attribute is used for an enum and it is not valid.""" 14 | 15 | try: 16 | enum_cls(text.upper()) if isinstance(text, str) else text 17 | except ValueError: 18 | valid_modes = ", ".join(f"'{mode.value}'" for mode in enum_cls) 19 | logger.warning(f"Invalid attribute: '{enum_cls.__name__}.{text}'. Must be one of: {valid_modes}") 20 | 21 | 22 | @typechecked 23 | def sqlite_config( 24 | base_dir: Path, 25 | *, 26 | file_name: str = "db.sqlite3", 27 | engine: str = "django.db.backends.sqlite3", 28 | transaction_mode: TransactionMode | str = TransactionMode.IMMEDIATE, 29 | timeout: int = 5, 30 | init_command: str | None = None, 31 | journal_mode: JournalMode | str = JournalMode.WAL, 32 | synchronous: Synchronous | str = Synchronous.NORMAL, 33 | temp_store: TempStore | str = TempStore.MEMORY, 34 | mmap_size: int = 134217728, 35 | journal_size_limit: int = 27103364, 36 | cache_size: int = 2000, 37 | pragmas: dict[str, Any] | None = None, 38 | ): 39 | """Generate a Django database configuration dictionary for SQLite. It provides sensible defaults 40 | while allowing customization of key SQLite parameters. 41 | 42 | Args: 43 | base_dir: Directory where the database file will be stored. 44 | file_name: Name of the SQLite database file. Defaults to 'db.sqlite3'. 45 | engine: Django database backend to use. Defaults to 'django.db.backends.sqlite3'. 46 | transaction_mode: The transaction locking behavior. Defaults to 'IMMEDIATE'. 47 | timeout: Time in seconds to wait for a database lock before raising an error. Defaults to 5. 48 | init_command: Custom SQL command to execute when the database connection is created. 49 | If None, will be generated from other parameters. 50 | journal_mode: The journal mode for the database. Defaults to 'WAL'. 51 | synchronous: How aggressively SQLite syncs data to disk. Defaults to 'NORMAL'. 52 | temp_store: How to store temporary objects. Defaults to 'MEMORY'. 53 | mmap_size: Maximum number of bytes to use for memory-mapped I/O. Defaults to 134217728. 54 | journal_size_limit: Maximum size of the journal in bytes. Defaults to 27103364. 55 | cache_size: Maximum number of database disk pages to hold in memory. Defaults to 2000. 56 | pragmas: Additional PRAGMA statements to include in the init command. 57 | These will override any conflicting settings from other parameters. 58 | 59 | Returns: 60 | A dictionary containing the database configuration for Django's DATABASES setting. 61 | 62 | Example: 63 | ```python 64 | from pathlib import Path 65 | from dj_lite import sqlite_config 66 | 67 | DATABASES = { 68 | 'default': sqlite_config( 69 | base_dir=Path(__file__).parent, 70 | file_name='myapp.db', 71 | cache_size=4000 72 | ) 73 | } 74 | ``` 75 | """ 76 | 77 | warn_on_invalid_enum(TransactionMode, transaction_mode) 78 | warn_on_invalid_enum(JournalMode, journal_mode) 79 | warn_on_invalid_enum(Synchronous, synchronous) 80 | warn_on_invalid_enum(TempStore, temp_store) 81 | 82 | if pragmas is None: 83 | pragmas = {} 84 | 85 | # Get default pragmas based on kwargs passed in if there isn't an init_command passed-in 86 | if not init_command: 87 | init_command = "" 88 | 89 | default_pragmas = { 90 | "journal_mode": journal_mode, 91 | "synchronous": synchronous, 92 | "temp_store": temp_store, 93 | "mmap_size": mmap_size, 94 | "journal_size_limit": journal_size_limit, 95 | "cache_size": cache_size, 96 | } 97 | 98 | pragmas.update({k: v for k, v in default_pragmas.items() if k not in pragmas}) 99 | 100 | # Update the init_command with an extra pragmas 101 | if pragmas: 102 | for key, value in pragmas.items(): 103 | init_command = f"""{init_command}PRAGMA {key}={value}; 104 | """ 105 | 106 | config = { 107 | "ENGINE": engine, 108 | "NAME": base_dir / file_name, 109 | "OPTIONS": { 110 | "transaction_mode": str(transaction_mode), 111 | "timeout": timeout, 112 | "init_command": init_command, 113 | }, 114 | } 115 | 116 | return config 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dj-lite 💡 2 | 3 | >Use SQLite in production with Django 4 | 5 | ## Overview 6 | 7 | Simplify deploying and maintaining production Django websites by using SQLite in production. `dj-lite` helps enable the best performance for SQLite for small to medium-sized projects. 8 | 9 | `dj-lite` requires Django 5.1+ because it utilizes the `init_command` and `transaction_mode` [configuration options](https://docs.djangoproject.com/en/5.2/releases/5.1/#database-backends). 10 | 11 | I wrote a [definitive guide to using Django SQLite in production](https://alldjango.com/articles/definitive-guide-to-using-django-sqlite-in-production) which has more details about the actual server setup and operations. It covers [Litestream](https://litestream.io/), deployments, Docker settings, and more. 12 | 13 | Also read through the [official Django SQLite notes](https://docs.djangoproject.com/en/stable/ref/databases/#sqlite-notes) for additional low-level information. 14 | 15 | ## Installation 16 | 17 | 1. Install `dj-lite` with `pip`, `uv`, `poetry`, etc. 18 | 19 | ```bash 20 | pip install dj-lite 21 | 22 | OR 23 | 24 | uv add dj-lite 25 | ``` 26 | 27 | 2. In `settings.py` add the following. 28 | 29 | ```python 30 | # settings.py 31 | 32 | from dj_lite import sqlite_config 33 | 34 | DATABASES = { 35 | "default": sqlite_config(BASE_DIR), 36 | } 37 | ``` 38 | 39 | 3. That's it! You're all set to go with the default configuration. 40 | 41 | ## Default configuration 42 | 43 | ```python 44 | { 45 | "ENGINE": "django.db.backends.sqlite3", 46 | "NAME": base_dir / Path("db.sqlite3"), 47 | "OPTIONS": { 48 | "transaction_mode": "IMMEDIATE", 49 | "timeout": 5, 50 | "init_command": """PRAGMA journal_mode=WAL; 51 | PRAGMA synchronous=NORMAL; 52 | PRAGMA temp_store=MEMORY; 53 | PRAGMA mmap_size=134217728; 54 | PRAGMA journal_size_limit=27103364; 55 | PRAGMA cache_size=2000;""", 56 | }, 57 | } 58 | ``` 59 | 60 | ## Args and Kwargs 61 | 62 | The `sqlite_config` method takes in many arguments to tweak the database settings. 63 | 64 | ### base_dir: `Path` 65 | 66 | The directory where the database file will be stored. Required. 67 | 68 | ### file_name: `str` 69 | 70 | Name of the SQLite database file. Defaults to `"db.sqlite3"`. 71 | 72 | ### engine: `str` 73 | 74 | Django database backend to use. Defaults to `"django.db.backends.sqlite3"`. 75 | 76 | ### transaction_mode: `TransactionMode` 77 | 78 | The transaction locking behavior. Defaults to `TransactionMode.IMMEDIATE`. 79 | 80 | ### timeout: `int` 81 | 82 | Time in seconds to wait for a database lock before raising an error. Defaults to `5`. 83 | 84 | ### init_command: `str` | None 85 | 86 | Custom SQL command to execute when the database connection is created. If `None`, will be generated from other parameters. 87 | 88 | ### journal_mode: `JournalMode` 89 | 90 | The journal mode for the database. Defaults to `JournalMode.WAL`. 91 | 92 | ### synchronous: `Synchronous` 93 | 94 | How aggressively SQLite syncs data to disk. Defaults to `Synchronous.NORMAL`. 95 | 96 | ### temp_store: `TempStore` 97 | 98 | How to store temporary objects. Defaults to `TempStore.MEMORY`. 99 | 100 | ### mmap_size: `int` 101 | 102 | Maximum number of bytes to use for memory-mapped I/O. Defaults to `134217728`. 103 | 104 | ### journal_size_limit: `int` 105 | 106 | Maximum size of the journal in bytes. Defaults to `27103364`. 107 | 108 | ### cache_size: `int` 109 | 110 | Maximum number of database disk pages to hold in memory. Defaults to `2000`. 111 | 112 | ### pragmas: `dict` | `None` 113 | 114 | Additional PRAGMA statements to include in the init command. These will override any conflicting settings from other parameters. 115 | 116 | ## What is even happening here? 117 | 118 | The Django defaults for SQLite are fine for local dev or running tests, but they are not great for production use -- specifically when there are concurrent reads/writes to the database. `dj-lite` tunes SQLite so it can be safely used in production. 119 | 120 | ### Pragmas 121 | 122 | When SQLite opens a database connection, settings (called [`pragmas`](https://sqlite.org/pragma.html)) can be passed in to tune the performance. `dj-lite` comes with highly tuned defaults for these `pragmas`. 123 | 124 | ``` 125 | PRAGMA journal_mode=WAL; 126 | PRAGMA synchronous=NORMAL; 127 | PRAGMA temp_store=MEMORY; 128 | PRAGMA mmap_size=134217728; 129 | PRAGMA journal_size_limit=27103364; 130 | PRAGMA cache_size=2000; 131 | ``` 132 | 133 | #### What about the foreign_keys pragma? 134 | 135 | You might notice that the SQLite `foreign_keys` `pragma` is not included above. That is because it is the one `pragma` that is [always passed in when Django creates a connection to SQLite](https://github.com/django/django/blob/7a80e29feaa675a27bf525164502ebc8ecbdce1a/django/db/backends/sqlite3/base.py#L209). 136 | 137 | ### Transaction Mode 138 | 139 | According to the [Django documentation](https://docs.djangoproject.com/en/stable/ref/databases/#transactions-behavior), SQLite supports three transaction modes: `DEFERRED`, `IMMEDIATE`, and `EXCLUSIVE` -- the default is `DEFERRED`. However, "[to] make sure your transactions wait until timeout before raising “Database is Locked”, change the transaction mode to `IMMEDIATE`." 140 | 141 | In my experience, using `IMMEDIATE` has been ok as long as database queries are short. 142 | 143 | ## Inspiration 144 | 145 | - [Litestack](https://github.com/oldmoe/litestack) 146 | - [Django, SQLite, and the Database is Locked Error](https://blog.pecar.me/django-sqlite-dblock) 147 | - [Gotchas with SQLite in Production](https://blog.pecar.me/sqlite-prod) 148 | - [Django SQLite Production Config](https://blog.pecar.me/sqlite-django-config) 149 | - [DjangoCon Europe 2024 | Django, SQLite, and Production](youtube.com/watch?v=GTDYwEXv-sE) 150 | - [DjangoCon Europe 2023 | Use SQLite in production](https://www.youtube.com/watch?v=yTicYJDT1zE) 151 | 152 | ## Developing 153 | 154 | ### Run the tests 155 | 156 | 1. `uv pip install -e .` 157 | 2. `uv run pytest` 158 | -------------------------------------------------------------------------------- /tests/configurator/test_sqlite_config.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from pathlib import Path 3 | 4 | import pytest 5 | from typeguard import TypeCheckError 6 | 7 | from dj_lite import ( 8 | SQLITE_INIT_COMMAND, 9 | JournalMode, 10 | Synchronous, 11 | TransactionMode, 12 | sqlite_config, 13 | ) 14 | from dj_lite.enums import TempStore 15 | 16 | default = { 17 | "ENGINE": "django.db.backends.sqlite3", 18 | "NAME": Path("db.sqlite3"), 19 | "OPTIONS": { 20 | "transaction_mode": "IMMEDIATE", 21 | "timeout": 5, 22 | "init_command": SQLITE_INIT_COMMAND, 23 | }, 24 | } 25 | 26 | 27 | def test_default(): 28 | expected = deepcopy(default) 29 | 30 | actual = sqlite_config(Path(".")) 31 | 32 | assert actual == expected 33 | 34 | 35 | def test_file_name(): 36 | expected = deepcopy(default) 37 | expected["NAME"] = Path("custom_db.sqlite3") 38 | 39 | actual = sqlite_config(Path("."), file_name="custom_db.sqlite3") 40 | 41 | assert actual == expected 42 | 43 | 44 | def test_engine(): 45 | expected = deepcopy(default) 46 | expected["ENGINE"] = "custom.engine.path" 47 | 48 | actual = sqlite_config(Path("."), engine="custom.engine.path") 49 | 50 | assert actual == expected 51 | 52 | 53 | def test_transaction_mode_deferred(): 54 | expected = deepcopy(default) 55 | expected["OPTIONS"]["transaction_mode"] = "DEFERRED" 56 | 57 | actual = sqlite_config(Path("."), transaction_mode=TransactionMode.DEFERRED) 58 | 59 | assert actual == expected 60 | 61 | 62 | def test_transaction_mode_exclusive(): 63 | expected = deepcopy(default) 64 | expected["OPTIONS"]["transaction_mode"] = "EXCLUSIVE" 65 | 66 | actual = sqlite_config(Path("."), transaction_mode=TransactionMode.EXCLUSIVE) 67 | 68 | assert actual == expected 69 | 70 | 71 | def test_invalid_transaction_mode(caplog): 72 | sqlite_config(Path("."), transaction_mode="fake-transaction-mode") 73 | 74 | assert 1 == len(caplog.records) 75 | assert "Invalid attribute: 'TransactionMode.fake-transaction-mode'" in caplog.records[0].message 76 | 77 | 78 | def test_timeout(): 79 | expected = deepcopy(default) 80 | expected["OPTIONS"]["timeout"] = 30 81 | 82 | actual = sqlite_config(Path("."), timeout=30) 83 | 84 | assert actual == expected 85 | 86 | 87 | def test_init_command(): 88 | init_command = """ 89 | PRAGMA journal_mode=WAL; 90 | PRAGMA synchronous=FULL;""" 91 | 92 | expected = deepcopy(default) 93 | expected["OPTIONS"]["init_command"] = init_command 94 | 95 | actual = sqlite_config(Path("."), init_command=init_command) 96 | 97 | assert actual == expected 98 | 99 | 100 | def test_cache_size(): 101 | expected = deepcopy(default) 102 | expected["OPTIONS"]["init_command"] = expected["OPTIONS"]["init_command"].replace( 103 | "PRAGMA cache_size=2000", "PRAGMA cache_size=1" 104 | ) 105 | 106 | actual = sqlite_config(Path("."), cache_size=1) 107 | 108 | assert actual == expected 109 | 110 | 111 | def test_journal_mode(): 112 | expected = deepcopy(default) 113 | expected["OPTIONS"]["init_command"] = expected["OPTIONS"]["init_command"].replace( 114 | "PRAGMA journal_mode=WAL;", "PRAGMA journal_mode=DELETE;" 115 | ) 116 | 117 | actual = sqlite_config(Path("."), journal_mode=JournalMode.DELETE) 118 | 119 | assert actual == expected 120 | 121 | 122 | def test_invalid_journal_mode(caplog): 123 | sqlite_config(Path("."), journal_mode="fake-journal-mode") 124 | 125 | assert 1 == len(caplog.records) 126 | assert "Invalid attribute: 'JournalMode.fake-journal-mode'" in caplog.records[0].message 127 | 128 | 129 | def test_synchronous(): 130 | expected = deepcopy(default) 131 | expected["OPTIONS"]["init_command"] = expected["OPTIONS"]["init_command"].replace( 132 | "PRAGMA synchronous=NORMAL;", "PRAGMA synchronous=FULL;" 133 | ) 134 | 135 | actual = sqlite_config(Path("."), synchronous=Synchronous.FULL) 136 | 137 | assert actual == expected 138 | 139 | 140 | def test_invalid_synchronous(caplog): 141 | sqlite_config(Path("."), synchronous="fake-synchronous") 142 | 143 | assert 1 == len(caplog.records) 144 | assert "Invalid attribute: 'Synchronous.fake-synchronous'" in caplog.records[0].message 145 | 146 | 147 | def test_temp_store(): 148 | expected = deepcopy(default) 149 | expected["OPTIONS"]["init_command"] = expected["OPTIONS"]["init_command"].replace( 150 | "PRAGMA temp_store=MEMORY;", "PRAGMA temp_store=FILE;" 151 | ) 152 | 153 | actual = sqlite_config(Path("."), temp_store=TempStore.FILE) 154 | 155 | assert actual == expected 156 | 157 | 158 | def test_invalid_temp_store(caplog): 159 | sqlite_config(Path("."), temp_store="fake-temp-store") 160 | 161 | assert 1 == len(caplog.records) 162 | assert "Invalid attribute: 'TempStore.fake-temp-store'" in caplog.records[0].message 163 | 164 | 165 | def test_mmap_size(): 166 | expected = deepcopy(default) 167 | expected["OPTIONS"]["init_command"] = expected["OPTIONS"]["init_command"].replace( 168 | "PRAGMA mmap_size=134217728;", "PRAGMA mmap_size=268435456;" 169 | ) 170 | 171 | actual = sqlite_config(Path("."), mmap_size=268435456) 172 | 173 | assert actual == expected 174 | 175 | 176 | def test_journal_size_limit(): 177 | expected = deepcopy(default) 178 | expected["OPTIONS"]["init_command"] = expected["OPTIONS"]["init_command"].replace( 179 | "PRAGMA journal_size_limit=27103364;", "PRAGMA journal_size_limit=10000000;" 180 | ) 181 | 182 | actual = sqlite_config(Path("."), journal_size_limit=10000000) 183 | 184 | assert actual == expected 185 | 186 | 187 | def test_incorrect_type(): 188 | with pytest.raises(TypeCheckError) as e: 189 | sqlite_config(Path("."), journal_size_limit="ohno") 190 | 191 | assert e.exconly() == 'typeguard.TypeCheckError: argument "journal_size_limit" (str) is not an instance of int' 192 | 193 | 194 | def test_extra_kwargs(): 195 | expected = deepcopy(default) 196 | expected["OPTIONS"]["init_command"] = f"""PRAGMA something_extra=123; 197 | {expected["OPTIONS"]["init_command"]}""" 198 | 199 | actual = sqlite_config(Path("."), pragmas={"something_extra": 123}) 200 | 201 | assert actual == expected 202 | 203 | 204 | def test_extra_kwargs_overrides(): 205 | expected = deepcopy(default) 206 | expected["OPTIONS"]["init_command"] = expected["OPTIONS"]["init_command"].replace( 207 | "PRAGMA journal_mode=WAL;", "PRAGMA journal_mode=OOPS;" 208 | ) 209 | 210 | actual = sqlite_config(Path("."), pragmas={"journal_mode": "OOPS"}) 211 | 212 | assert actual == expected 213 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.9.7" 4 | 5 | [manifest] 6 | constraints = [{ name = "django", specifier = ">=5.1" }] 7 | 8 | [[package]] 9 | name = "colorama" 10 | version = "0.4.6" 11 | source = { registry = "https://pypi.org/simple" } 12 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 13 | wheels = [ 14 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 15 | ] 16 | 17 | [[package]] 18 | name = "coverage" 19 | version = "7.10.6" 20 | source = { registry = "https://pypi.org/simple" } 21 | sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736 } 22 | wheels = [ 23 | { url = "https://files.pythonhosted.org/packages/a8/1d/2e64b43d978b5bd184e0756a41415597dfef30fcbd90b747474bd749d45f/coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", size = 217025 }, 24 | { url = "https://files.pythonhosted.org/packages/23/62/b1e0f513417c02cc10ef735c3ee5186df55f190f70498b3702d516aad06f/coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", size = 217419 }, 25 | { url = "https://files.pythonhosted.org/packages/e7/16/b800640b7a43e7c538429e4d7223e0a94fd72453a1a048f70bf766f12e96/coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", size = 244180 }, 26 | { url = "https://files.pythonhosted.org/packages/fb/6f/5e03631c3305cad187eaf76af0b559fff88af9a0b0c180d006fb02413d7a/coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", size = 245992 }, 27 | { url = "https://files.pythonhosted.org/packages/eb/a1/f30ea0fb400b080730125b490771ec62b3375789f90af0bb68bfb8a921d7/coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", size = 247851 }, 28 | { url = "https://files.pythonhosted.org/packages/02/8e/cfa8fee8e8ef9a6bb76c7bef039f3302f44e615d2194161a21d3d83ac2e9/coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", size = 245891 }, 29 | { url = "https://files.pythonhosted.org/packages/93/a9/51be09b75c55c4f6c16d8d73a6a1d46ad764acca0eab48fa2ffaef5958fe/coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", size = 243909 }, 30 | { url = "https://files.pythonhosted.org/packages/e9/a6/ba188b376529ce36483b2d585ca7bdac64aacbe5aa10da5978029a9c94db/coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", size = 244786 }, 31 | { url = "https://files.pythonhosted.org/packages/d0/4c/37ed872374a21813e0d3215256180c9a382c3f5ced6f2e5da0102fc2fd3e/coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1", size = 219521 }, 32 | { url = "https://files.pythonhosted.org/packages/8e/36/9311352fdc551dec5b973b61f4e453227ce482985a9368305880af4f85dd/coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528", size = 220417 }, 33 | { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129 }, 34 | { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532 }, 35 | { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931 }, 36 | { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864 }, 37 | { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969 }, 38 | { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659 }, 39 | { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714 }, 40 | { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351 }, 41 | { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562 }, 42 | { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453 }, 43 | { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127 }, 44 | { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324 }, 45 | { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560 }, 46 | { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053 }, 47 | { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802 }, 48 | { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935 }, 49 | { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855 }, 50 | { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974 }, 51 | { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409 }, 52 | { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724 }, 53 | { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536 }, 54 | { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171 }, 55 | { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351 }, 56 | { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600 }, 57 | { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600 }, 58 | { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206 }, 59 | { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478 }, 60 | { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637 }, 61 | { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529 }, 62 | { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143 }, 63 | { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770 }, 64 | { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566 }, 65 | { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195 }, 66 | { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059 }, 67 | { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287 }, 68 | { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625 }, 69 | { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801 }, 70 | { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027 }, 71 | { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576 }, 72 | { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341 }, 73 | { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468 }, 74 | { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429 }, 75 | { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493 }, 76 | { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757 }, 77 | { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331 }, 78 | { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607 }, 79 | { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663 }, 80 | { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197 }, 81 | { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551 }, 82 | { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553 }, 83 | { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486 }, 84 | { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981 }, 85 | { url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054 }, 86 | { url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851 }, 87 | { url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429 }, 88 | { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080 }, 89 | { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293 }, 90 | { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800 }, 91 | { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965 }, 92 | { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220 }, 93 | { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660 }, 94 | { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417 }, 95 | { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567 }, 96 | { url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831 }, 97 | { url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950 }, 98 | { url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969 }, 99 | { url = "https://files.pythonhosted.org/packages/91/70/f73ad83b1d2fd2d5825ac58c8f551193433a7deaf9b0d00a8b69ef61cd9a/coverage-7.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352", size = 217009 }, 100 | { url = "https://files.pythonhosted.org/packages/01/e8/099b55cd48922abbd4b01ddd9ffa352408614413ebfc965501e981aced6b/coverage-7.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612", size = 217400 }, 101 | { url = "https://files.pythonhosted.org/packages/ee/d1/c6bac7c9e1003110a318636fef3b5c039df57ab44abcc41d43262a163c28/coverage-7.10.6-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b", size = 243835 }, 102 | { url = "https://files.pythonhosted.org/packages/01/f9/82c6c061838afbd2172e773156c0aa84a901d59211b4975a4e93accf5c89/coverage-7.10.6-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144", size = 245658 }, 103 | { url = "https://files.pythonhosted.org/packages/81/6a/35674445b1d38161148558a3ff51b0aa7f0b54b1def3abe3fbd34efe05bc/coverage-7.10.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b", size = 247433 }, 104 | { url = "https://files.pythonhosted.org/packages/18/27/98c99e7cafb288730a93535092eb433b5503d529869791681c4f2e2012a8/coverage-7.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862", size = 245315 }, 105 | { url = "https://files.pythonhosted.org/packages/09/05/123e0dba812408c719c319dea05782433246f7aa7b67e60402d90e847545/coverage-7.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2", size = 243385 }, 106 | { url = "https://files.pythonhosted.org/packages/67/52/d57a42502aef05c6325f28e2e81216c2d9b489040132c18725b7a04d1448/coverage-7.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78", size = 244343 }, 107 | { url = "https://files.pythonhosted.org/packages/6b/22/7f6fad7dbb37cf99b542c5e157d463bd96b797078b1ec506691bc836f476/coverage-7.10.6-cp39-cp39-win32.whl", hash = "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c", size = 219530 }, 108 | { url = "https://files.pythonhosted.org/packages/62/30/e2fda29bfe335026027e11e6a5e57a764c9df13127b5cf42af4c3e99b937/coverage-7.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf", size = 220432 }, 109 | { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986 }, 110 | ] 111 | 112 | [package.optional-dependencies] 113 | toml = [ 114 | { name = "tomli", marker = "python_full_version <= '3.11'" }, 115 | ] 116 | 117 | [[package]] 118 | name = "dj-lite" 119 | version = "1.3.0" 120 | source = { editable = "." } 121 | dependencies = [ 122 | { name = "typeguard" }, 123 | ] 124 | 125 | [package.dev-dependencies] 126 | dev = [ 127 | { name = "mypy" }, 128 | { name = "pytest" }, 129 | { name = "pytest-cov" }, 130 | { name = "ruff" }, 131 | ] 132 | 133 | [package.metadata] 134 | requires-dist = [{ name = "typeguard", specifier = ">=2" }] 135 | 136 | [package.metadata.requires-dev] 137 | dev = [ 138 | { name = "mypy", specifier = ">=1.15.0" }, 139 | { name = "pytest", specifier = ">=8.4.1" }, 140 | { name = "pytest-cov", specifier = ">0" }, 141 | { name = "ruff", specifier = ">=0.11.8" }, 142 | ] 143 | 144 | [[package]] 145 | name = "exceptiongroup" 146 | version = "1.3.0" 147 | source = { registry = "https://pypi.org/simple" } 148 | dependencies = [ 149 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 150 | ] 151 | sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } 152 | wheels = [ 153 | { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, 154 | ] 155 | 156 | [[package]] 157 | name = "importlib-metadata" 158 | version = "8.7.0" 159 | source = { registry = "https://pypi.org/simple" } 160 | dependencies = [ 161 | { name = "zipp" }, 162 | ] 163 | sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } 164 | wheels = [ 165 | { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, 166 | ] 167 | 168 | [[package]] 169 | name = "iniconfig" 170 | version = "2.1.0" 171 | source = { registry = "https://pypi.org/simple" } 172 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } 173 | wheels = [ 174 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, 175 | ] 176 | 177 | [[package]] 178 | name = "mypy" 179 | version = "1.17.1" 180 | source = { registry = "https://pypi.org/simple" } 181 | dependencies = [ 182 | { name = "mypy-extensions" }, 183 | { name = "pathspec" }, 184 | { name = "tomli", marker = "python_full_version < '3.11'" }, 185 | { name = "typing-extensions" }, 186 | ] 187 | sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570 } 188 | wheels = [ 189 | { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299 }, 190 | { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451 }, 191 | { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211 }, 192 | { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687 }, 193 | { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322 }, 194 | { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962 }, 195 | { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009 }, 196 | { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482 }, 197 | { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883 }, 198 | { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215 }, 199 | { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956 }, 200 | { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307 }, 201 | { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295 }, 202 | { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355 }, 203 | { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285 }, 204 | { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895 }, 205 | { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025 }, 206 | { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664 }, 207 | { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338 }, 208 | { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066 }, 209 | { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473 }, 210 | { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296 }, 211 | { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657 }, 212 | { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320 }, 213 | { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037 }, 214 | { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550 }, 215 | { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963 }, 216 | { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189 }, 217 | { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322 }, 218 | { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879 }, 219 | { url = "https://files.pythonhosted.org/packages/29/cb/673e3d34e5d8de60b3a61f44f80150a738bff568cd6b7efb55742a605e98/mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9", size = 10992466 }, 220 | { url = "https://files.pythonhosted.org/packages/0c/d0/fe1895836eea3a33ab801561987a10569df92f2d3d4715abf2cfeaa29cb2/mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99", size = 10117638 }, 221 | { url = "https://files.pythonhosted.org/packages/97/f3/514aa5532303aafb95b9ca400a31054a2bd9489de166558c2baaeea9c522/mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8", size = 11915673 }, 222 | { url = "https://files.pythonhosted.org/packages/ab/c3/c0805f0edec96fe8e2c048b03769a6291523d509be8ee7f56ae922fa3882/mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8", size = 12649022 }, 223 | { url = "https://files.pythonhosted.org/packages/45/3e/d646b5a298ada21a8512fa7e5531f664535a495efa672601702398cea2b4/mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259", size = 12895536 }, 224 | { url = "https://files.pythonhosted.org/packages/14/55/e13d0dcd276975927d1f4e9e2ec4fd409e199f01bdc671717e673cc63a22/mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d", size = 9512564 }, 225 | { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411 }, 226 | ] 227 | 228 | [[package]] 229 | name = "mypy-extensions" 230 | version = "1.1.0" 231 | source = { registry = "https://pypi.org/simple" } 232 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } 233 | wheels = [ 234 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, 235 | ] 236 | 237 | [[package]] 238 | name = "packaging" 239 | version = "25.0" 240 | source = { registry = "https://pypi.org/simple" } 241 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } 242 | wheels = [ 243 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, 244 | ] 245 | 246 | [[package]] 247 | name = "pathspec" 248 | version = "0.12.1" 249 | source = { registry = "https://pypi.org/simple" } 250 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } 251 | wheels = [ 252 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, 253 | ] 254 | 255 | [[package]] 256 | name = "pluggy" 257 | version = "1.6.0" 258 | source = { registry = "https://pypi.org/simple" } 259 | sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } 260 | wheels = [ 261 | { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, 262 | ] 263 | 264 | [[package]] 265 | name = "pygments" 266 | version = "2.19.2" 267 | source = { registry = "https://pypi.org/simple" } 268 | sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } 269 | wheels = [ 270 | { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, 271 | ] 272 | 273 | [[package]] 274 | name = "pytest" 275 | version = "8.4.1" 276 | source = { registry = "https://pypi.org/simple" } 277 | dependencies = [ 278 | { name = "colorama", marker = "sys_platform == 'win32'" }, 279 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 280 | { name = "iniconfig" }, 281 | { name = "packaging" }, 282 | { name = "pluggy" }, 283 | { name = "pygments" }, 284 | { name = "tomli", marker = "python_full_version < '3.11'" }, 285 | ] 286 | sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } 287 | wheels = [ 288 | { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, 289 | ] 290 | 291 | [[package]] 292 | name = "pytest-cov" 293 | version = "6.2.1" 294 | source = { registry = "https://pypi.org/simple" } 295 | dependencies = [ 296 | { name = "coverage", extra = ["toml"] }, 297 | { name = "pluggy" }, 298 | { name = "pytest" }, 299 | ] 300 | sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432 } 301 | wheels = [ 302 | { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644 }, 303 | ] 304 | 305 | [[package]] 306 | name = "ruff" 307 | version = "0.12.11" 308 | source = { registry = "https://pypi.org/simple" } 309 | sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103 } 310 | wheels = [ 311 | { url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885 }, 312 | { url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364 }, 313 | { url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111 }, 314 | { url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060 }, 315 | { url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848 }, 316 | { url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288 }, 317 | { url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633 }, 318 | { url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430 }, 319 | { url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133 }, 320 | { url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082 }, 321 | { url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490 }, 322 | { url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928 }, 323 | { url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513 }, 324 | { url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154 }, 325 | { url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653 }, 326 | { url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270 }, 327 | { url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600 }, 328 | { url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290 }, 329 | ] 330 | 331 | [[package]] 332 | name = "tomli" 333 | version = "2.2.1" 334 | source = { registry = "https://pypi.org/simple" } 335 | sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } 336 | wheels = [ 337 | { 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 }, 338 | { 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 }, 339 | { 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 }, 340 | { 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 }, 341 | { 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 }, 342 | { 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 }, 343 | { 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 }, 344 | { 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 }, 345 | { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, 346 | { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, 347 | { 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 }, 348 | { 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 }, 349 | { 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 }, 350 | { 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 }, 351 | { 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 }, 352 | { 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 }, 353 | { 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 }, 354 | { 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 }, 355 | { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, 356 | { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, 357 | { 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 }, 358 | { 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 }, 359 | { 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 }, 360 | { 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 }, 361 | { 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 }, 362 | { 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 }, 363 | { 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 }, 364 | { 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 }, 365 | { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, 366 | { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, 367 | { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, 368 | ] 369 | 370 | [[package]] 371 | name = "typeguard" 372 | version = "4.4.4" 373 | source = { registry = "https://pypi.org/simple" } 374 | dependencies = [ 375 | { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, 376 | { name = "typing-extensions" }, 377 | ] 378 | sdist = { url = "https://files.pythonhosted.org/packages/c7/68/71c1a15b5f65f40e91b65da23b8224dad41349894535a97f63a52e462196/typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74", size = 75203 } 379 | wheels = [ 380 | { url = "https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e", size = 34874 }, 381 | ] 382 | 383 | [[package]] 384 | name = "typing-extensions" 385 | version = "4.14.1" 386 | source = { registry = "https://pypi.org/simple" } 387 | sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } 388 | wheels = [ 389 | { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, 390 | ] 391 | 392 | [[package]] 393 | name = "zipp" 394 | version = "3.23.0" 395 | source = { registry = "https://pypi.org/simple" } 396 | sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } 397 | wheels = [ 398 | { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, 399 | ] 400 | --------------------------------------------------------------------------------