diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index de4d401..fbe0c17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,16 @@ jobs: - name: uv tree run: uv tree + #---------------------------------------------- + #---- Linting and Static Analysis + #---------------------------------------------- + + - name: 🔎 Run Ruff + run: uv run ruff check . # Or 'ruff format --check .' if you want formatting checks + + - name: 🐍 Mypy Static Type Checker + run: uv run mypy . + #---------------------------------------------- #---- Pre-Checks #---------------------------------------------- diff --git a/DictDataBase.code-workspace b/DictDataBase.code-workspace index 5ddb053..29fc669 100644 --- a/DictDataBase.code-workspace +++ b/DictDataBase.code-workspace @@ -7,10 +7,14 @@ "settings": { "[python]": { "editor.formatOnSave": true, - "editor.defaultFormatter": "charliermarsh.ruff" - }, - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.codeActionsOnSave": { + "source.fixAll": "never", + "source.organizeImports": "explicit", + }, }, + "python.analysis.typeCheckingMode": "standard", + "python.analysis.diagnosticMode": "workspace", + "python.terminal.activateEnvironment": false, } } diff --git a/dictdatabase/io_bytes.py b/dictdatabase/io_bytes.py index 276a0c3..1c68168 100644 --- a/dictdatabase/io_bytes.py +++ b/dictdatabase/io_bytes.py @@ -1,10 +1,12 @@ +from __future__ import annotations + import os import zlib from . import config, utils -def read(db_name: str, *, start: int = None, end: int = None) -> bytes: +def read(db_name: str, *, start: int | None = None, end: int | None = None) -> bytes: """ Read the content of a file as bytes. Reading works even when the config changes, so a compressed ddb file can also be read if compression is @@ -33,7 +35,8 @@ def read(db_name: str, *, start: int = None, end: int = None) -> bytes: if json_exists: if ddb_exists: - raise FileExistsError(f'Inconsistent: "{db_name}" exists as .json and .ddb.' "Please remove one of them.") + msg = f'Inconsistent: "{db_name}" exists as .json and .ddb.Please remove one of them.' + raise FileExistsError(msg) with open(json_path, "rb") as f: if start is None and end is None: return f.read() @@ -43,7 +46,8 @@ def read(db_name: str, *, start: int = None, end: int = None) -> bytes: return f.read() return f.read(end - start) if not ddb_exists: - raise FileNotFoundError(f'No database file exists for "{db_name}"') + msg = f'No database file exists for "{db_name}"' + raise FileNotFoundError(msg) with open(ddb_path, "rb") as f: json_bytes = zlib.decompress(f.read()) if start is None and end is None: @@ -53,7 +57,7 @@ def read(db_name: str, *, start: int = None, end: int = None) -> bytes: return json_bytes[start:end] -def write(db_name: str, dump: bytes, *, start: int = None) -> None: +def write(db_name: str, dump: bytes, *, start: int | None = None) -> None: """ Write the bytes to the file of the db_path. If the db was compressed but no compression is enabled, remove the compressed file, and vice versa. @@ -72,7 +76,8 @@ def write(db_name: str, dump: bytes, *, start: int = None) -> None: remove_file = None if config.use_compression: if start is not None: - raise RuntimeError("Cannot write to compressed file at a specific index") + msg = "Cannot write to compressed file at a specific index" + raise RuntimeError(msg) write_file = ddb_path if json_exists: remove_file = json_path diff --git a/dictdatabase/io_safe.py b/dictdatabase/io_safe.py index d6586a0..4564152 100644 --- a/dictdatabase/io_safe.py +++ b/dictdatabase/io_safe.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import os from . import config, io_unsafe, locking, utils -def read(file_name: str) -> dict: +def read(file_name: str) -> dict | None: """ Read the content of a file as a dict. @@ -20,7 +22,7 @@ def read(file_name: str) -> dict: return io_unsafe.read(file_name) -def partial_read(file_name: str, key: str) -> dict: +def partial_read(file_name: str, key: str) -> dict | None: """ Read only the value of a key-value pair from a file. diff --git a/dictdatabase/locking.py b/dictdatabase/locking.py index 498bc39..312bc22 100644 --- a/dictdatabase/locking.py +++ b/dictdatabase/locking.py @@ -40,7 +40,7 @@ class LockFileMeta: Metadata representation for a lock file. """ - __slots__ = ("ddb_dir", "name", "id", "time_ns", "stage", "mode", "path") + __slots__ = ("ddb_dir", "id", "mode", "name", "path", "stage", "time_ns") ddb_dir: str name: str @@ -79,7 +79,7 @@ class FileLocksSnapshot: On init, orphaned locks are removed. """ - __slots__ = ("any_has_locks", "any_write_locks", "any_has_write_locks", "locks") + __slots__ = ("any_has_locks", "any_has_write_locks", "any_write_locks", "locks") locks: list[LockFileMeta] any_has_locks: bool @@ -142,7 +142,7 @@ class AbstractLock: provides a blueprint for derived classes to implement. """ - __slots__ = ("db_name", "need_lock", "has_lock", "snapshot", "mode", "is_alive" "keep_alive_thread") + __slots__ = ("db_name", "has_lock", "is_alive", "keep_alive_thread", "mode", "need_lock", "snapshot") db_name: str need_lock: LockFileMeta @@ -150,12 +150,12 @@ class AbstractLock: snapshot: FileLocksSnapshot mode: str is_alive: bool - keep_alive_thread: threading.Thread + keep_alive_thread: threading.Thread | None def __init__(self, db_name: str) -> None: # Normalize db_name to avoid file naming conflicts self.db_name = db_name.replace("/", "___").replace(".", "____") - time_ns = time.time_ns() + time_ns = str(time.time_ns()) t_id = f"{threading.get_native_id()}" # ID that's unique across processes and threads. dir = os.path.join(config.storage_directory, ".ddb") @@ -197,7 +197,8 @@ def _start_keep_alive_thread(self) -> None: """ if self.keep_alive_thread is not None: - raise RuntimeError("Keep alive thread already exists.") + msg = "Keep alive thread already exists." + raise RuntimeError(msg) self.is_alive = True self.keep_alive_thread = threading.Thread(target=self._keep_alive_thread, daemon=False) @@ -227,7 +228,7 @@ def _unlock(self) -> None: def __enter__(self) -> None: self._lock() - def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ANN001 + def __exit__(self, exc_type, exc_val, exc_tb) -> None: self._unlock() @@ -248,7 +249,8 @@ def _lock(self) -> None: # If this thread already holds a read lock, raise an exception. if self.snapshot.exists(self.has_lock): os.unlink(self.need_lock.path) - raise RuntimeError("Thread already has a read lock. Do not try to obtain a read lock twice.") + msg = "Thread already has a read lock. Do not try to obtain a read lock twice." + raise RuntimeError(msg) start_time = time.time() @@ -264,7 +266,8 @@ def _lock(self) -> None: return time.sleep(SLEEP_TIMEOUT) if time.time() - start_time > AQUIRE_LOCK_TIMEOUT: - raise RuntimeError("Timeout while waiting for read lock.") + msg = "Timeout while waiting for read lock." + raise RuntimeError(msg) self.snapshot = FileLocksSnapshot(self.need_lock) @@ -285,7 +288,8 @@ def _lock(self) -> None: # If this thread already holds a write lock, raise an exception. if self.snapshot.exists(self.has_lock): os.unlink(self.need_lock.path) - raise RuntimeError("Thread already has a write lock. Do not try to obtain a write lock twice.") + msg = "Thread already has a write lock. Do not try to obtain a write lock twice." + raise RuntimeError(msg) start_time = time.time() @@ -299,5 +303,6 @@ def _lock(self) -> None: return time.sleep(SLEEP_TIMEOUT) if time.time() - start_time > AQUIRE_LOCK_TIMEOUT: - raise RuntimeError("Timeout while waiting for write lock.") + msg = "Timeout while waiting for write lock." + raise RuntimeError(msg) self.snapshot = FileLocksSnapshot(self.need_lock) diff --git a/dictdatabase/models.py b/dictdatabase/models.py index fdc2822..0f7093f 100644 --- a/dictdatabase/models.py +++ b/dictdatabase/models.py @@ -36,9 +36,11 @@ def __init__(self, path: str, key: str, where: Callable) -> None: self.key = key is not None if self.key and self.where: - raise TypeError("Cannot specify both key and where") + msg = "Cannot specify both key and where" + raise TypeError(msg) if self.key and self.dir: - raise TypeError("Cannot specify sub-key when selecting a folder. Specify the key in the path instead.") + msg = "Cannot specify sub-key when selecting a folder. Specify the key in the path instead." + raise TypeError(msg) @property def file_normal(self) -> bool: @@ -61,7 +63,7 @@ def dir_where(self) -> bool: return self.dir and self.where and not self.key -def at(*path, key: str = None, where: Callable[[Any, Any], bool] = None) -> DDBMethodChooser: +def at(*path, key: str | None = None, where: Callable[[Any, Any], bool] | None = None) -> DDBMethodChooser: """ Select a file or folder to perform an operation on. If you want to select a specific key in a file, use the `key` parameter, @@ -88,7 +90,7 @@ def at(*path, key: str = None, where: Callable[[Any, Any], bool] = None) -> DDBM class DDBMethodChooser: - __slots__ = ("path", "key", "where", "op_type") + __slots__ = ("key", "op_type", "path", "where") path: str key: str @@ -98,8 +100,8 @@ class DDBMethodChooser: def __init__( self, path: tuple, - key: str = None, - where: Callable[[Any, Any], bool] = None, + key: str | None = None, + where: Callable[[Any, Any], bool] | None = None, ) -> None: # Convert path to a list of strings pc = [] @@ -124,7 +126,8 @@ def exists(self) -> bool: As long it exists as a key in any dict, it will be found. """ if self.where is not None: - raise RuntimeError("DDB.at(where=...).exists() cannot be used with the where parameter") + msg = "DDB.at(where=...).exists() cannot be used with the where parameter" + raise RuntimeError(msg) if not utils.file_exists(self.path): return False @@ -146,13 +149,13 @@ def create(self, data: dict | None = None, force_overwrite: bool = False) -> Non exists, defaults to False (optional). """ if self.where is not None or self.key is not None: - raise RuntimeError("DDB.at().create() cannot be used with the where or key parameters") + msg = "DDB.at().create() cannot be used with the where or key parameters" + raise RuntimeError(msg) # Except if db exists and force_overwrite is False if not force_overwrite and self.exists(): - raise FileExistsError( - f"Database {self.path} already exists in {config.storage_directory}. Pass force_overwrite=True to overwrite." - ) + msg = f"Database {self.path} already exists in {config.storage_directory}. Pass force_overwrite=True to overwrite." + raise FileExistsError(msg) # Write db to file if data is None: data = {} @@ -163,10 +166,11 @@ def delete(self) -> None: Delete the file at the selected path. """ if self.where is not None or self.key is not None: - raise RuntimeError("DDB.at().delete() cannot be used with the where or key parameters") + msg = "DDB.at().delete() cannot be used with the where or key parameters" + raise RuntimeError(msg) io_safe.delete(self.path) - def read(self, as_type: Type[T] = None) -> dict | T | None: + def read(self, as_type: Type[T] | None = None) -> dict | T | None: """ Reads a file or folder depending on previous `.at(...)` selection. @@ -180,7 +184,7 @@ def type_cast(value): return value return as_type(value) - data = {} + data: dict = {} if self.op_type.file_normal: data = io_safe.read(self.path) @@ -209,7 +213,7 @@ def type_cast(value): return type_cast(data) def session( - self, as_type: Type[T] = None + self, as_type: Type[T] | None = None ) -> SessionFileFull[T] | SessionFileKey[T] | SessionFileWhere[T] | SessionDirFull[T] | SessionDirWhere[T]: """ Opens a session to the selected file(s) or folder, depending on previous @@ -228,6 +232,7 @@ def session( Returns: - Tuple of (session_object, data) """ + if self.op_type.file_normal: return SessionFileFull(self.path, as_type) if self.op_type.file_key: @@ -238,3 +243,6 @@ def session( return SessionDirFull(self.path, as_type) if self.op_type.dir_where: return SessionDirWhere(self.path, self.where, as_type) + + msg = "Invalid operation type" + raise RuntimeError(msg) diff --git a/dictdatabase/sessions.py b/dictdatabase/sessions.py index 36e1a38..65d433e 100644 --- a/dictdatabase/sessions.py +++ b/dictdatabase/sessions.py @@ -13,12 +13,12 @@ def type_cast(obj, as_type): return obj if as_type is None else as_type(obj) -class SessionBase: +class SessionBase[T]: in_session: bool db_name: str as_type: T - def __init__(self, db_name: str, as_type): + def __init__(self, db_name: str, as_type: T) -> None: self.in_session = False self.db_name = db_name self.as_type = as_type @@ -27,7 +27,7 @@ def __enter__(self): self.in_session = True self.data_handle = {} - def __exit__(self, type, value, tb): + def __exit__(self, type, value, tb) -> None: write_lock = getattr(self, "write_lock", None) if write_lock is not None: if isinstance(write_lock, list): diff --git a/dictdatabase/utils.py b/dictdatabase/utils.py index 4fab884..cb3a6d3 100644 --- a/dictdatabase/utils.py +++ b/dictdatabase/utils.py @@ -83,7 +83,8 @@ def seek_index_through_value_bytes(json_bytes: bytes, index: int) -> int: while True: i = json_bytes.find(byte_codes.QUOTE, i + 1) if i == -1: - raise TypeError("Invalid JSON") + msg = "Invalid JSON" + raise TypeError(msg) j = i - 1 backslash_count = 0 @@ -123,7 +124,8 @@ def seek_index_through_value_bytes(json_bytes: bytes, index: int) -> int: return i i += 1 - raise TypeError("Invalid JSON") + msg = "Invalid JSON" + raise TypeError(msg) def count_nesting_in_bytes(json_bytes: bytes, start: int, end: int) -> int: @@ -173,9 +175,9 @@ def find_outermost_key_in_json_bytes(json_bytes: bytes, key: str) -> Tuple[int, # TODO: Very strict. the key must have a colon directly after it # For example {"a": 1} will work, but {"a" : 1} will not work! - key = f'"{key}":'.encode() + key_bytes = f'"{key}":'.encode() - if (curr_i := json_bytes.find(key, 0)) == -1: + if (curr_i := json_bytes.find(key_bytes, 0)) == -1: return (-1, -1) # Assert: Key was found and curr_i is the index of the first character of the key @@ -184,8 +186,8 @@ def find_outermost_key_in_json_bytes(json_bytes: bytes, key: str) -> Tuple[int, key_nest = [(curr_i, count_nesting_in_bytes(json_bytes, 0, curr_i))] # As long as more keys are found, keep track of them and their nesting level - while (next_i := json_bytes.find(key, curr_i + len(key))) != -1: - nesting = count_nesting_in_bytes(json_bytes, curr_i + len(key), next_i) + while (next_i := json_bytes.find(key_bytes, curr_i + len(key_bytes))) != -1: + nesting = count_nesting_in_bytes(json_bytes, curr_i + len(key_bytes), next_i) key_nest.append((next_i, nesting)) curr_i = next_i @@ -195,7 +197,7 @@ def find_outermost_key_in_json_bytes(json_bytes: bytes, key: str) -> Tuple[int, # Early exit if there is only one key if len(key_nest) == 1: index, level = key_nest[0] - return (index, index + len(key)) if level == 1 else (-1, -1) + return (index, index + len(key_bytes)) if level == 1 else (-1, -1) # Relative to total nesting for i in range(1, len(key_nest)): @@ -205,7 +207,7 @@ def find_outermost_key_in_json_bytes(json_bytes: bytes, key: str) -> Tuple[int, indices_at_index_one = [i for i, level in key_nest if level == 1] if len(indices_at_index_one) != 1: return (-1, -1) - return (indices_at_index_one[0], indices_at_index_one[0] + len(key)) + return (indices_at_index_one[0], indices_at_index_one[0] + len(key_bytes)) def detect_indentation_in_json_bytes(json_bytes: bytes, index: int) -> Tuple[int, str]: @@ -221,7 +223,7 @@ def detect_indentation_in_json_bytes(json_bytes: bytes, index: int) -> Tuple[int - A tuple of the indentation level and the whitespace used """ - indentation_bytes, contains_tab = bytes(), False + indentation_bytes, contains_tab = b"", False for i in range(index - 1, -1, -1): if json_bytes[i] not in [byte_codes.SPACE, byte_codes.TAB]: break diff --git a/pyproject.toml b/pyproject.toml index bcb573a..74500d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dev = [ "pytest-cov ~= 4.0.0", "path-dict ~= 3.0.4", "ruff>=0.11.6", + "mypy>=1.14.1", ] @@ -41,28 +42,30 @@ package = true [tool.ruff] show-fixes = true line-length = 120 -select = [ - "ANN", # annotations - "B", # bugbear - "C", # comprehensions - "E", # style errors - "F", # flakes - "I", # import sorting - "M", # meta - "N", # naming - "U", # upgrade - "W", # style warnings - "YTT", # sys.version -] +select = ["ALL"] ignore = [ + "D", # Docstrings "E501", # line length "UP007", # use X | Y for union (not possible in python 3.8) "UP006", # Use typing.Tuple for python 3.8 support "W191", # indentation contains tabs "E741", # ambiguous variable name + "COM812", # Trailing comma missing + "ANN202", # Missing return type annotation for private function + "ANN001", # Missing type annotation for function argument + "S101", # Use of `assert` detected + "ANN201", # Missing return type annotation for public function + "ARG001", # Unused function argument + "T201", # `print` found + "ERA001", # Found commented-out code ] [tool.ruff.format] indent-style = "tab" quote-style = "double" + + + +[tool.mypy] +ignore_missing_imports = true diff --git a/tests/benchmark/locking.py b/tests/benchmark/locking.py index 325a12d..c9c4d30 100644 --- a/tests/benchmark/locking.py +++ b/tests/benchmark/locking.py @@ -15,9 +15,9 @@ # 25.11.22: 4156ms with profiler.Profiler() as p: for _ in range(25_000): - l = locking.ReadLock("db") - l._lock() - l._unlock() + read_lock = locking.ReadLock("db") + read_lock._lock() # noqa: SLF001 + read_lock._unlock() # noqa: SLF001 p.open_in_browser() @@ -25,14 +25,14 @@ # 25.11.22: 4159ms with profiler.Profiler() as p: for _ in range(25_000): - l = locking.WriteLock("db") - l._lock() - l._unlock() + write_lock = locking.WriteLock("db") + write_lock._lock() # noqa: SLF001 + write_lock._unlock() # noqa: SLF001 p.open_in_browser() -l = locking.WriteLock("db/test.some") -l._lock() +write_lock = locking.WriteLock("db/test.some") +write_lock._lock() # noqa: SLF001 shutil.rmtree(DDB.config.storage_directory) diff --git a/tests/benchmark/run_parallel.py b/tests/benchmark/run_parallel.py index 26e5909..c529082 100644 --- a/tests/benchmark/run_parallel.py +++ b/tests/benchmark/run_parallel.py @@ -1,11 +1,13 @@ +from __future__ import annotations + import json import os import random import shutil import time -from calendar import c from dataclasses import dataclass from multiprocessing import Pool +from typing import Callable from path_dict import PathDict @@ -14,7 +16,7 @@ DDB.config.storage_directory = ".ddb_bench_multi" -def benchmark(iterations, setup: callable = None): +def benchmark(iterations, setup: Callable | None = None): def decorator(function): def wrapper(*args, **kwargs): f_name = function.__name__ @@ -107,7 +109,7 @@ def parallel_stressor(scenario: Scenario): # Create Tables for t in range(scenario.files): if scenario.big_file: - with open(os.path.join(os.getcwd(), "test_db/production_database/tasks.json"), "r") as f: + with open(os.path.join(os.getcwd(), "test_db/production_database/tasks.json")) as f: db = json.loads(f.read()) db["counter"] = {"counter": 0} else: diff --git a/tests/system_checks/test_monotonic_over_threads.py b/tests/system_checks/test_monotonic_over_threads.py index 3d6c35f..10ce6a0 100644 --- a/tests/system_checks/test_monotonic_over_threads.py +++ b/tests/system_checks/test_monotonic_over_threads.py @@ -1,6 +1,7 @@ import queue import threading import time +from typing import Callable # Number of threads NUM_THREADS = 64 @@ -16,17 +17,17 @@ } # Queue to store timestamps in order -timestamps = queue.Queue() +timestamps: queue.Queue = queue.Queue() -def capture_time(i, clock_func: callable) -> None: +def capture_time(i, clock_func: Callable) -> None: # Capture time using the given clock function and put it in the queue for _ in range(1000): # print(f"Thread {i} capturing time") timestamps.put(clock_func()) -def check_monotonicity_for_clock(clock_name: str, clock_func: callable) -> None: +def check_monotonicity_for_clock(clock_name: str, clock_func: Callable) -> None: # Clear the queue for the next clock while not timestamps.empty(): timestamps.get() diff --git a/tests/system_checks/test_tick_rate.py b/tests/system_checks/test_tick_rate.py index e2834e2..0dc3829 100644 --- a/tests/system_checks/test_tick_rate.py +++ b/tests/system_checks/test_tick_rate.py @@ -1,7 +1,8 @@ import time +from typing import Callable -def get_tick_rate(clock_func: callable) -> float: +def get_tick_rate(clock_func: Callable) -> float: start_time = time.time() measurements = [clock_func() for _ in range(2_000_000)] end_time = time.time() @@ -10,7 +11,8 @@ def get_tick_rate(clock_func: callable) -> float: prev_value = measurements[0] for current_value in measurements[1:]: if current_value < prev_value: - raise RuntimeError("Clock function is not monotonic") + msg = "Clock function is not monotonic" + raise RuntimeError(msg) if current_value != prev_value: ticks += 1 prev_value = current_value diff --git a/uv.lock b/uv.lock index f7ff59a..1054b51 100644 --- a/uv.lock +++ b/uv.lock @@ -191,6 +191,8 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "mypy", version = "1.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "mypy", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "path-dict" }, { name = "pyinstrument" }, { name = "pytest-cov" }, @@ -203,6 +205,7 @@ requires-dist = [{ name = "orjson", specifier = ">=3.9,<4.0" }] [package.metadata.requires-dev] dev = [ + { name = "mypy", specifier = ">=1.14.1" }, { name = "path-dict", specifier = "~=3.0.4" }, { name = "pyinstrument", specifier = "~=4.4.0" }, { name = "pytest-cov", specifier = "~=4.0.0" }, @@ -228,6 +231,115 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, ] +[[package]] +name = "mypy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "mypy-extensions", marker = "python_full_version < '3.9'" }, + { name = "tomli", marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002 }, + { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400 }, + { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172 }, + { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732 }, + { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197 }, + { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836 }, + { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432 }, + { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515 }, + { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791 }, + { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203 }, + { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900 }, + { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869 }, + { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668 }, + { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060 }, + { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167 }, + { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341 }, + { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991 }, + { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016 }, + { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097 }, + { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728 }, + { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965 }, + { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660 }, + { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198 }, + { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 }, + { url = "https://files.pythonhosted.org/packages/39/02/1817328c1372be57c16148ce7d2bfcfa4a796bedaed897381b1aad9b267c/mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31", size = 11143050 }, + { url = "https://files.pythonhosted.org/packages/b9/07/99db9a95ece5e58eee1dd87ca456a7e7b5ced6798fd78182c59c35a7587b/mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6", size = 10321087 }, + { url = "https://files.pythonhosted.org/packages/9a/eb/85ea6086227b84bce79b3baf7f465b4732e0785830726ce4a51528173b71/mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319", size = 12066766 }, + { url = "https://files.pythonhosted.org/packages/4b/bb/f01bebf76811475d66359c259eabe40766d2f8ac8b8250d4e224bb6df379/mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac", size = 12787111 }, + { url = "https://files.pythonhosted.org/packages/2f/c9/84837ff891edcb6dcc3c27d85ea52aab0c4a34740ff5f0ccc0eb87c56139/mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b", size = 12974331 }, + { url = "https://files.pythonhosted.org/packages/84/5f/901e18464e6a13f8949b4909535be3fa7f823291b8ab4e4b36cfe57d6769/mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837", size = 9763210 }, + { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493 }, + { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702 }, + { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104 }, + { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167 }, + { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834 }, + { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231 }, + { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +dependencies = [ + { name = "mypy-extensions", marker = "python_full_version >= '3.9'" }, + { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433 }, + { url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472 }, + { url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424 }, + { url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450 }, + { url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765 }, + { url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701 }, + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338 }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540 }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051 }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751 }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783 }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618 }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, + { url = "https://files.pythonhosted.org/packages/5a/fa/79cf41a55b682794abe71372151dbbf856e3008f6767057229e6649d294a/mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078", size = 10737129 }, + { url = "https://files.pythonhosted.org/packages/d3/33/dd8feb2597d648de29e3da0a8bf4e1afbda472964d2a4a0052203a6f3594/mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba", size = 9856335 }, + { url = "https://files.pythonhosted.org/packages/e4/b5/74508959c1b06b96674b364ffeb7ae5802646b32929b7701fc6b18447592/mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5", size = 11611935 }, + { url = "https://files.pythonhosted.org/packages/6c/53/da61b9d9973efcd6507183fdad96606996191657fe79701b2c818714d573/mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b", size = 12365827 }, + { url = "https://files.pythonhosted.org/packages/c1/72/965bd9ee89540c79a25778cc080c7e6ef40aa1eeac4d52cec7eae6eb5228/mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2", size = 12541924 }, + { url = "https://files.pythonhosted.org/packages/46/d0/f41645c2eb263e6c77ada7d76f894c580c9ddb20d77f0c24d34273a4dab2/mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980", size = 9271176 }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + [[package]] name = "orjson" version = "3.10.15" @@ -573,3 +685,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, ] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +]