From 59c7d2c5788896f8c1fc6778dce2aefb7d598461 Mon Sep 17 00:00:00 2001 From: Konstantin Alekseev Date: Thu, 28 May 2026 08:25:56 +0300 Subject: [PATCH 1/3] update model constraints, add utils.csp --- django-stubs/contrib/admin/tests.pyi | 32 +++++------ django-stubs/db/models/constraints.pyi | 73 ++++++++++++++------------ django-stubs/middleware/csp.pyi | 10 ++++ django-stubs/utils/csp.pyi | 36 +++++++++++++ s/sync-files.txt | 8 ++- 5 files changed, 108 insertions(+), 51 deletions(-) create mode 100644 django-stubs/middleware/csp.pyi create mode 100644 django-stubs/utils/csp.pyi diff --git a/django-stubs/contrib/admin/tests.pyi b/django-stubs/contrib/admin/tests.pyi index 2d01ce0c2..dcfd3d375 100644 --- a/django-stubs/contrib/admin/tests.pyi +++ b/django-stubs/contrib/admin/tests.pyi @@ -1,25 +1,27 @@ from collections.abc import Callable +from contextlib import AbstractContextManager from typing import Any from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.test.selenium import SeleniumTestCase -from django.utils.deprecation import MiddlewareMixin +from django.utils.csp import CSP as CSP from django.utils.functional import _StrOrPromise -class CSPMiddleware(MiddlewareMixin): ... - class AdminSeleniumTestCase(SeleniumTestCase, StaticLiveServerTestCase): - def wait_until(self, callback: Callable[..., Any], timeout: int = ...) -> None: ... - def wait_for_popup(self, num_windows: int = ..., timeout: int = ...) -> None: ... - def wait_for(self, css_selector: str, timeout: int = ...) -> None: ... - def wait_for_text(self, css_selector: str, text: str, timeout: int = ...) -> None: ... - def wait_for_value(self, css_selector: str, text: str, timeout: int = ...) -> None: ... - def wait_until_visible(self, css_selector: str, timeout: int = ...) -> None: ... - def wait_until_invisible(self, css_selector: str, timeout: int = ...) -> None: ... - def wait_page_loaded(self) -> None: ... - def admin_login(self, username: str, password: str, login_url: _StrOrPromise = ...) -> None: ... - def get_css_value(self, selector: str, attribute: str) -> Any: ... - def get_select_option(self, selector: str, value: Any) -> Any: ... + def wait_until(self, callback: Callable[..., Any], timeout: int = 10) -> None: ... + def wait_for_and_switch_to_popup(self, num_windows: int = 2, timeout: int = 10) -> None: ... + def wait_for(self, css_selector: str, timeout: int = 10) -> None: ... + def wait_for_text(self, css_selector: str, text: str, timeout: int = 10) -> None: ... + def wait_for_value(self, css_selector: str, text: str, timeout: int = 10) -> None: ... + def wait_until_visible(self, css_selector: str, timeout: int = 10) -> None: ... + def wait_until_invisible(self, css_selector: str, timeout: int = 10) -> None: ... + def wait_page_ready(self, timeout: int = 10) -> None: ... + def wait_page_loaded(self, timeout: int = 10) -> AbstractContextManager[None]: ... + def trigger_resize(self) -> None: ... + def admin_login(self, username: str, password: str, login_url: _StrOrPromise = "/admin/") -> None: ... + def select_option(self, selector: str, value: Any) -> None: ... + def deselect_option(self, selector: str, value: Any) -> None: ... + def assertCountSeleniumElements(self, selector: str, count: int, root_element: Any = None) -> None: ... def assertSelectOptions(self, selector: str, values: Any) -> None: ... def assertSelectedOptions(self, selector: str, values: Any) -> None: ... - def has_css_class(self, selector: str, klass: str) -> bool: ... + def is_disabled(self, selector: str) -> bool: ... diff --git a/django-stubs/db/models/constraints.pyi b/django-stubs/db/models/constraints.pyi index d19ff77bf..8c7bd0ae7 100644 --- a/django-stubs/db/models/constraints.pyi +++ b/django-stubs/db/models/constraints.pyi @@ -2,12 +2,14 @@ from collections.abc import Sequence from enum import Enum from typing import Any, cast, overload +from django.core.checks import CheckMessage +from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.models.base import Model from django.db.models.expressions import BaseExpression, Combinable from django.db.models.query_utils import Q from django.utils.functional import _StrOrPromise -from typing_extensions import Self, deprecated +from typing_extensions import Self, override class Deferrable(Enum): DEFERRED = cast(str, ...) @@ -15,55 +17,47 @@ class Deferrable(Enum): class BaseConstraint: name: str + violation_error_code: str | None + violation_error_message: _StrOrPromise | None + default_violation_error_message: _StrOrPromise + non_db_attrs: tuple[str, ...] def __init__( self, - *args: BaseExpression | Combinable | str, - name: str | None = ..., - violation_error_message: _StrOrPromise | None = ..., + *, + name: str, + violation_error_code: str | None = None, + violation_error_message: _StrOrPromise | None = None, ) -> None: ... - def constraint_sql( - self, - model: type[Model] | None, - schema_editor: BaseDatabaseSchemaEditor | None, - ) -> str: ... - def create_sql( - self, - model: type[Model] | None, - schema_editor: BaseDatabaseSchemaEditor | None, - ) -> str: ... - def remove_sql( - self, - model: type[Model] | None, - schema_editor: BaseDatabaseSchemaEditor | None, - ) -> str: ... - def deconstruct(self) -> Any: ... + @property + def contains_expressions(self) -> bool: ... + def constraint_sql(self, model: type[Model] | None, schema_editor: BaseDatabaseSchemaEditor | None) -> str: ... + def create_sql(self, model: type[Model] | None, schema_editor: BaseDatabaseSchemaEditor | None) -> str: ... + def remove_sql(self, model: type[Model] | None, schema_editor: BaseDatabaseSchemaEditor | None) -> str: ... + def validate( + self, model: type[Model], instance: Model, exclude: set[str] | None = None, using: str = "default" + ) -> None: ... + def get_violation_error_message(self) -> _StrOrPromise | dict[str, Any]: ... + def check(self, model: type[Model], connection: BaseDatabaseWrapper) -> list[CheckMessage]: ... + def deconstruct(self) -> tuple[str, Sequence[Any], dict[str, Any]]: ... def clone(self) -> Self: ... class CheckConstraint(BaseConstraint): - check: Q | BaseExpression condition: Q | BaseExpression - @overload - @deprecated("check keyword argument is deprecated in favor of condition and will be removed in Django 6.0") - def __init__( - self, - *, - name: str, - condition: None = None, - check: Q | BaseExpression, - violation_error_code: str | None = None, - violation_error_message: _StrOrPromise | None = None, - ) -> None: ... - @overload def __init__( self, *, name: str, condition: Q | BaseExpression, - check: None = None, violation_error_code: str | None = None, violation_error_message: _StrOrPromise | None = None, ) -> None: ... + @override + def check(self, model: type[Model], connection: BaseDatabaseWrapper) -> list[CheckMessage]: ... + @override + def validate( + self, model: type[Model], instance: Model, exclude: set[str] | None = None, using: str = "default" + ) -> None: ... class UniqueConstraint(BaseConstraint): expressions: Sequence[BaseExpression | Combinable] @@ -100,3 +94,14 @@ class UniqueConstraint(BaseConstraint): violation_error_code: str | None = None, violation_error_message: _StrOrPromise | None = None, ) -> None: ... + @property + @override + def contains_expressions(self) -> bool: ... + @override + def check(self, model: type[Model], connection: BaseDatabaseWrapper) -> list[CheckMessage]: ... + @override + def validate( + self, model: type[Model], instance: Model, exclude: set[str] | None = None, using: str = "default" + ) -> None: ... + +__all__ = ["BaseConstraint", "CheckConstraint", "Deferrable", "UniqueConstraint"] diff --git a/django-stubs/middleware/csp.pyi b/django-stubs/middleware/csp.pyi new file mode 100644 index 000000000..53efcc6e3 --- /dev/null +++ b/django-stubs/middleware/csp.pyi @@ -0,0 +1,10 @@ +from django.http import HttpRequest, HttpResponse +from django.utils.csp import CSP as CSP +from django.utils.csp import LazyNonce +from django.utils.deprecation import MiddlewareMixin + +def get_nonce(request: HttpRequest) -> LazyNonce | None: ... + +class ContentSecurityPolicyMiddleware(MiddlewareMixin): + def process_request(self, request: HttpRequest) -> None: ... + def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse: ... diff --git a/django-stubs/utils/csp.pyi b/django-stubs/utils/csp.pyi new file mode 100644 index 000000000..ca85c56ce --- /dev/null +++ b/django-stubs/utils/csp.pyi @@ -0,0 +1,36 @@ +import sys +from collections.abc import Collection, Mapping + +from django.utils.functional import SimpleLazyObject +from typing_extensions import override + +if sys.version_info >= (3, 11): + from enum import StrEnum as _StrEnum +else: + from enum import Enum + + class _ReprEnum(Enum): ... # type: ignore[misc] + class _StrEnum(str, _ReprEnum): ... # type: ignore[misc] + +class CSP(_StrEnum): + HEADER_ENFORCE = "Content-Security-Policy" + HEADER_REPORT_ONLY = "Content-Security-Policy-Report-Only" + + NONE = "'none'" + REPORT_SAMPLE = "'report-sample'" + SELF = "'self'" + STRICT_DYNAMIC = "'strict-dynamic'" + UNSAFE_EVAL = "'unsafe-eval'" + UNSAFE_HASHES = "'unsafe-hashes'" + UNSAFE_INLINE = "'unsafe-inline'" + WASM_UNSAFE_EVAL = "'wasm-unsafe-eval'" + + NONCE = "" + +class LazyNonce(SimpleLazyObject): + def __init__(self) -> None: ... + @override + def __bool__(self) -> bool: ... + +def generate_nonce() -> str: ... +def build_policy(config: Mapping[str, Collection[str] | str], nonce: SimpleLazyObject | str | None = None) -> str: ... diff --git a/s/sync-files.txt b/s/sync-files.txt index d8d1389a8..7cc51a95a 100644 --- a/s/sync-files.txt +++ b/s/sync-files.txt @@ -25,11 +25,10 @@ django-stubs/core/validators.pyi django-stubs/utils/decorators.pyi django-stubs/db/models/__init__.pyi #django-stubs/db/models/query.pyi -django-stubs/contrib/admin/decorators.pyi +django-stubs/db/models/constraints.pyi #django-stubs/db/models/aggregates.pyi #django-stubs/db/models/sql/compiler.pyi #django-stubs/db/models/sql/query.pyi -#django-stubs/contrib/admin/options.pyi #django-stubs/db/models/sql/subqueries.pyi django-stubs/db/models/sql/__init__.pyi #django-stubs/db/models/sql/where.pyi @@ -57,6 +56,8 @@ django-stubs/forms/formsets.pyi django-stubs/forms/renderers.pyi #django-stubs/forms/widgets.pyi #django-stubs/utils/datastructures.pyi +django-stubs/contrib/admin/decorators.pyi +#django-stubs/contrib/admin/options.pyi django-stubs/contrib/contenttypes/admin.pyi django-stubs/contrib/sites/admin.pyi django-stubs/contrib/redirects/admin.pyi @@ -68,5 +69,8 @@ django-stubs/template/exceptions.pyi django-stubs/template/library.pyi django-stubs/template/loader.pyi django-stubs/utils/safestring.pyi +django-stubs/middleware/csp.pyi +django-stubs/utils/csp.pyi +django-stubs/contrib/admin/tests.pyi #django-stubs/utils/html.pyi django-stubs/core/signing.pyi From 37160356992c20d08547a85ad26e58e2d64ad54d Mon Sep 17 00:00:00 2001 From: Konstantin Alekseev Date: Thu, 28 May 2026 08:29:49 +0300 Subject: [PATCH 2/3] drop pre-commit, it's broken and unused --- .pre-commit-config.yaml | 28 -------- pyproject.toml | 1 - uv.lock | 156 ---------------------------------------- 3 files changed, 185 deletions(-) delete mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 590d5c2c0..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: trailing-whitespace - - id: check-executables-have-shebangs - - id: debug-statements - - id: end-of-file-fixer - - id: check-yaml - - id: check-toml - - id: check-merge-conflict - - id: mixed-line-ending - args: [--fix=lf] - - id: check-case-conflict - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.0 - hooks: - - id: ruff-check - args: ["--fix", "--exit-non-zero-on-fix"] - - id: ruff-format - - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 - hooks: - - id: codespell - additional_dependencies: - - tomli diff --git a/pyproject.toml b/pyproject.toml index af39bf8d6..4b993b92a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,6 @@ dev = [ "pyright>=1.1.406", "ruff>=0.1.0", "tomlkit>=0.12.1", - "pre-commit>=4.3.0", ] diff --git a/uv.lock b/uv.lock index 06a7b566a..438322db3 100644 --- a/uv.lock +++ b/uv.lock @@ -60,15 +60,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/46/d3ec57ad500f598d1554bd14ce4df615960549ab2844961bc4e1f5fbd174/ast_serialize-0.3.0-cp39-abi3-win_arm64.whl", hash = "sha256:0dd00da29985f15f50dc35728b7e1e7c84507bccfea1d9914738530f1c72238a", size = 1077165, upload-time = "2026-04-30T23:24:46.377Z" }, ] -[[package]] -name = "cfgv" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -78,15 +69,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "distlib" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, -] - [[package]] name = "django" version = "5.2.14" @@ -110,7 +92,6 @@ source = { editable = "." } dev = [ { name = "django" }, { name = "mypy" }, - { name = "pre-commit" }, { name = "pyright" }, { name = "pytest" }, { name = "ruff" }, @@ -127,7 +108,6 @@ dev = [ dev = [ { name = "django", specifier = ">=5.2,<6.0" }, { name = "mypy", specifier = ">=2.1.0" }, - { name = "pre-commit", specifier = ">=4.3.0" }, { name = "pyright", specifier = ">=1.1.406" }, { name = "pytest", specifier = ">=7.1.1" }, { name = "ruff", specifier = ">=0.1.0" }, @@ -150,24 +130,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] -[[package]] -name = "filelock" -version = "3.29.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, -] - -[[package]] -name = "identify" -version = "2.6.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" @@ -357,15 +319,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] -[[package]] -name = "platformdirs" -version = "4.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -375,22 +328,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "pre-commit" -version = "4.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -431,83 +368,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] -[[package]] -name = "python-discovery" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/60/e88788207d81e46362cfbef0d4aaf4c0f49efc3c12d4c3fa3f542c34ebec/python_discovery-1.3.1.tar.gz", hash = "sha256:62f6db28064c9613e7ca76cb3f00c38c839a07c31c00dfe7ed0986493d2150a6", size = 68011, upload-time = "2026-05-12T20:53:36.336Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl", hash = "sha256:ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c", size = 33185, upload-time = "2026-05-12T20:53:34.969Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - [[package]] name = "ruff" version = "0.15.13" @@ -666,22 +526,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, ] -[[package]] -name = "virtualenv" -version = "21.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, - { name = "python-discovery" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/e1/665267cea4767debd19f584667a9197c2098b5e7f67a502da9f3a086ab37/virtualenv-21.3.2.tar.gz", hash = "sha256:3ecda97894a6fc1c53106356f488690e5c86278c1f693f3fc0805ac85a513686", size = 7613810, upload-time = "2026-05-12T14:44:18.01Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-py3-none-any.whl", hash = "sha256:c58ea748fa50bb2a4367da5ba3d30b02458ed40b4ea888faad94021f3309f764", size = 7594558, upload-time = "2026-05-12T14:44:15.193Z" }, -] - [[package]] name = "wheel" version = "0.47.0" From 9cab1d31c1b9fabcd538b7888cc7e50b7df4c350 Mon Sep 17 00:00:00 2001 From: Konstantin Alekseev Date: Thu, 28 May 2026 08:37:32 +0300 Subject: [PATCH 3/3] add django.tasks --- django-stubs/db/utils.pyi | 26 ++--- django-stubs/tasks/__init__.pyi | 28 ++++++ django-stubs/tasks/backends/__init__.pyi | 0 django-stubs/tasks/backends/base.pyi | 22 +++++ django-stubs/tasks/backends/dummy.pyi | 17 ++++ django-stubs/tasks/backends/immediate.pyi | 15 +++ django-stubs/tasks/base.pyi | 112 ++++++++++++++++++++++ django-stubs/tasks/checks.pyi | 7 ++ django-stubs/tasks/exceptions.pyi | 7 ++ django-stubs/tasks/signals.pyi | 19 ++++ django-stubs/utils/connection.pyi | 35 +++---- s/sync | 4 + 12 files changed, 262 insertions(+), 30 deletions(-) create mode 100644 django-stubs/tasks/__init__.pyi create mode 100644 django-stubs/tasks/backends/__init__.pyi create mode 100644 django-stubs/tasks/backends/base.pyi create mode 100644 django-stubs/tasks/backends/dummy.pyi create mode 100644 django-stubs/tasks/backends/immediate.pyi create mode 100644 django-stubs/tasks/base.pyi create mode 100644 django-stubs/tasks/checks.pyi create mode 100644 django-stubs/tasks/exceptions.pyi create mode 100644 django-stubs/tasks/signals.pyi diff --git a/django-stubs/db/utils.pyi b/django-stubs/db/utils.pyi index 7e7f49742..2a584a6eb 100644 --- a/django-stubs/db/utils.pyi +++ b/django-stubs/db/utils.pyi @@ -1,16 +1,13 @@ from collections.abc import Callable, Iterable from types import TracebackType -from typing import Any, ParamSpec, TypeVar -from typing import Any as Incomplete +from typing import Any from django.apps import AppConfig from django.db.backends.base.base import BaseDatabaseWrapper from django.db.models import Model from django.utils.connection import BaseConnectionHandler -from django.utils.connection import ConnectionDoesNotExist as ConnectionDoesNotExist - -_P = ParamSpec("_P") -_R = TypeVar("_R") +from django.utils.functional import cached_property +from typing_extensions import TypeVar, override DEFAULT_DB_ALIAS: str DJANGO_VERSION_PICKLE_KEY: str @@ -25,9 +22,11 @@ class InternalError(DatabaseError): ... class ProgrammingError(DatabaseError): ... class NotSupportedError(DatabaseError): ... +_F = TypeVar("_F", bound=Callable[..., Any]) + class DatabaseErrorWrapper: - wrapper: Incomplete - def __init__(self, wrapper: Incomplete) -> None: ... + def __init__(self, wrapper: Any) -> None: ... + def __del__(self) -> None: ... def __enter__(self) -> None: ... def __exit__( self, @@ -35,20 +34,21 @@ class DatabaseErrorWrapper: exc_value: BaseException | None, traceback: TracebackType | None, ) -> None: ... - def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R]: ... + def __call__(self, func: _F) -> _F: ... def load_backend(backend_name: str) -> Any: ... -class ConnectionHandler(BaseConnectionHandler): - settings_name: str # pyright: ignore[reportIncompatibleVariableOverride] - def configure_settings(self, databases: dict[str, dict[str, Any]] | None) -> dict[str, dict[str, Any]]: ... +class ConnectionHandler(BaseConnectionHandler[BaseDatabaseWrapper]): + @override + def configure_settings(self, databases: dict[str, Any] | None) -> dict[str, Any]: ... @property def databases(self) -> dict[str, dict[str, Any]]: ... + @override def create_connection(self, alias: str) -> BaseDatabaseWrapper: ... class ConnectionRouter: def __init__(self, routers: Iterable[Any] | None = None) -> None: ... - @property + @cached_property def routers(self) -> list[Any]: ... def db_for_read(self, model: type[Model], **hints: Any) -> str: ... def db_for_write(self, model: type[Model], **hints: Any) -> str: ... diff --git a/django-stubs/tasks/__init__.pyi b/django-stubs/tasks/__init__.pyi new file mode 100644 index 000000000..e63a1a3cb --- /dev/null +++ b/django-stubs/tasks/__init__.pyi @@ -0,0 +1,28 @@ +from django.utils.connection import BaseConnectionHandler + +from .backends.base import BaseTaskBackend +from .base import DEFAULT_TASK_BACKEND_ALIAS as DEFAULT_TASK_BACKEND_ALIAS +from .base import DEFAULT_TASK_QUEUE_NAME as DEFAULT_TASK_QUEUE_NAME +from .base import Task as Task +from .base import TaskContext as TaskContext +from .base import TaskResult as TaskResult +from .base import TaskResultStatus as TaskResultStatus +from .base import task as task + +__all__ = [ + "DEFAULT_TASK_BACKEND_ALIAS", + "DEFAULT_TASK_QUEUE_NAME", + "Task", + "TaskContext", + "TaskResult", + "TaskResultStatus", + "default_task_backend", + "task", + "task_backends", +] + +class TaskBackendHandler(BaseConnectionHandler[BaseTaskBackend]): ... + +task_backends: TaskBackendHandler +# Actually ConnectionProxy, but quacks exactly like BaseTaskBackend, it's not worth distinguishing the two. +default_task_backend: BaseTaskBackend diff --git a/django-stubs/tasks/backends/__init__.pyi b/django-stubs/tasks/backends/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/django-stubs/tasks/backends/base.pyi b/django-stubs/tasks/backends/base.pyi new file mode 100644 index 000000000..050f79195 --- /dev/null +++ b/django-stubs/tasks/backends/base.pyi @@ -0,0 +1,22 @@ +from abc import ABCMeta, abstractmethod +from typing import Any + +from django.tasks.base import Task, TaskResult + +class BaseTaskBackend(metaclass=ABCMeta): + task_class: type[Task] + supports_defer: bool + supports_async_task: bool + supports_get_result: bool + supports_priority: bool + alias: str + queues: list[str] + options: dict[str, Any] + def __init__(self, alias: str, params: dict[str, Any]) -> None: ... + def validate_task(self, task: Task) -> None: ... + @abstractmethod + def enqueue(self, task: Task, args: list[Any], kwargs: dict[str, Any]) -> TaskResult: ... + async def aenqueue(self, task: Task, args: list[Any], kwargs: dict[str, Any]) -> TaskResult: ... + def get_result(self, result_id: str) -> TaskResult: ... + async def aget_result(self, result_id: str) -> TaskResult: ... + def check(self, **kwargs: Any) -> list[Any]: ... diff --git a/django-stubs/tasks/backends/dummy.pyi b/django-stubs/tasks/backends/dummy.pyi new file mode 100644 index 000000000..eef304e5d --- /dev/null +++ b/django-stubs/tasks/backends/dummy.pyi @@ -0,0 +1,17 @@ +from typing import Any + +from django.tasks.base import Task, TaskResult +from typing_extensions import override + +from .base import BaseTaskBackend + +class DummyBackend(BaseTaskBackend): + results: list[TaskResult] + def __init__(self, alias: str, params: dict[str, Any]) -> None: ... + @override + def enqueue(self, task: Task, args: list[Any], kwargs: dict[str, Any]) -> TaskResult: ... + @override + def get_result(self, result_id: str) -> TaskResult: ... + @override + async def aget_result(self, result_id: str) -> TaskResult: ... + def clear(self) -> None: ... diff --git a/django-stubs/tasks/backends/immediate.pyi b/django-stubs/tasks/backends/immediate.pyi new file mode 100644 index 000000000..1b85365db --- /dev/null +++ b/django-stubs/tasks/backends/immediate.pyi @@ -0,0 +1,15 @@ +from logging import Logger +from typing import Any + +from django.tasks.base import Task, TaskResult +from typing_extensions import override + +from .base import BaseTaskBackend + +logger: Logger + +class ImmediateBackend(BaseTaskBackend): + worker_id: str + def __init__(self, alias: str, params: dict[str, Any]) -> None: ... + @override + def enqueue(self, task: Task, args: list[Any], kwargs: dict[str, Any]) -> TaskResult: ... diff --git a/django-stubs/tasks/base.pyi b/django-stubs/tasks/base.pyi new file mode 100644 index 000000000..d53fa99bb --- /dev/null +++ b/django-stubs/tasks/base.pyi @@ -0,0 +1,112 @@ +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Generic, overload + +from django.db.models.enums import TextChoices as TextChoices +from django.utils.module_loading import import_string as import_string +from django.utils.translation import pgettext_lazy as pgettext_lazy +from typing_extensions import ParamSpec, TypeVar + +from .backends.base import BaseTaskBackend +from .exceptions import TaskResultMismatch as TaskResultMismatch + +DEFAULT_TASK_BACKEND_ALIAS: str +DEFAULT_TASK_PRIORITY: int +DEFAULT_TASK_QUEUE_NAME: str +TASK_MAX_PRIORITY: int +TASK_MIN_PRIORITY: int +TASK_REFRESH_ATTRS: set[str] + +class TaskResultStatus(TextChoices): + READY = ... + RUNNING = ... + FAILED = ... + SUCCESSFUL = ... + +_P = ParamSpec("_P") +_R = TypeVar("_R") + +@dataclass(kw_only=True) +class Task(Generic[_P, _R]): + priority: int + func: Callable[_P, _R] + backend: str + queue_name: str + run_after: datetime | None + takes_context: bool = ... + def __post_init__(self) -> None: ... + @property + def name(self) -> str: ... + def using( + self, + *, + priority: int | None = None, + queue_name: str | None = None, + run_after: datetime | None = None, + backend: str | None = None, + ) -> Task[_P, _R]: ... + def enqueue(self, *args: _P.args, **kwargs: _P.kwargs) -> TaskResult[_P, _R]: ... + async def aenqueue(self, *args: _P.args, **kwargs: _P.kwargs) -> TaskResult[_P, _R]: ... + def get_result(self, result_id: str) -> TaskResult[_P, _R]: ... + async def aget_result(self, result_id: str) -> TaskResult[_P, _R]: ... + def call(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... + async def acall(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... + def get_backend(self) -> BaseTaskBackend: ... + @property + def module_path(self) -> str: ... + +@overload +def task( + function: Callable[_P, _R], + *, + priority: int | None = None, + queue_name: str | None = None, + backend: str | None = None, + takes_context: bool = ..., +) -> Task[_P, _R]: ... +@overload +def task( + *, + priority: int | None = None, + queue_name: str | None = None, + backend: str | None = None, + takes_context: bool = ..., +) -> Callable[[Callable[_P, _R]], Task[_P, _R]]: ... + +@dataclass(kw_only=True) +class TaskError: + exception_class_path: str + traceback: str + @property + def exception_class(self) -> type[BaseException]: ... + +@dataclass(kw_only=True) +class TaskResult(Generic[_P, _R]): + task: Task[_P, _R] + id: str + status: TaskResultStatus + enqueued_at: datetime | None + started_at: datetime | None + finished_at: datetime | None + last_attempted_at: datetime | None + args: list[Any] + kwargs: dict[str, Any] + backend: str + errors: list[TaskError] + worker_ids: list[str] + def __post_init__(self) -> None: ... + @property + def return_value(self) -> _R: ... + @property + def is_finished(self) -> bool: ... + @property + def attempts(self) -> int: ... + def refresh(self) -> None: ... + async def arefresh(self) -> None: ... + +@dataclass(kw_only=True) +class TaskContext(Generic[_P, _R]): + task_result: TaskResult[_P, _R] + @property + def attempt(self) -> int: ... diff --git a/django-stubs/tasks/checks.pyi b/django-stubs/tasks/checks.pyi new file mode 100644 index 000000000..68f7ae3bb --- /dev/null +++ b/django-stubs/tasks/checks.pyi @@ -0,0 +1,7 @@ +from collections.abc import Sequence +from typing import Any + +from django.apps.config import AppConfig +from django.core.checks.messages import CheckMessage + +def check_tasks(app_configs: Sequence[AppConfig] | None = ..., **kwargs: Any) -> list[CheckMessage]: ... diff --git a/django-stubs/tasks/exceptions.pyi b/django-stubs/tasks/exceptions.pyi new file mode 100644 index 000000000..1900b8304 --- /dev/null +++ b/django-stubs/tasks/exceptions.pyi @@ -0,0 +1,7 @@ +from django.core.exceptions import ImproperlyConfigured as ImproperlyConfigured + +class TaskException(Exception): ... +class InvalidTask(TaskException): ... +class InvalidTaskBackend(ImproperlyConfigured): ... +class TaskResultDoesNotExist(TaskException): ... +class TaskResultMismatch(TaskException): ... diff --git a/django-stubs/tasks/signals.pyi b/django-stubs/tasks/signals.pyi new file mode 100644 index 000000000..d1d338cf0 --- /dev/null +++ b/django-stubs/tasks/signals.pyi @@ -0,0 +1,19 @@ +from logging import Logger +from typing import Any + +from django.dispatch import Signal as Signal +from django.tasks.backends.base import BaseTaskBackend +from django.tasks.base import TaskResult + +from .base import TaskResultStatus as TaskResultStatus + +logger: Logger + +task_enqueued: Signal +task_finished: Signal +task_started: Signal + +def clear_tasks_handlers(*, setting: str, **kwargs: Any) -> None: ... +def log_task_enqueued(sender: type[BaseTaskBackend], task_result: TaskResult, **kwargs: Any) -> None: ... +def log_task_started(sender: type[BaseTaskBackend], task_result: TaskResult, **kwargs: Any) -> None: ... +def log_task_finished(sender: type[BaseTaskBackend], task_result: TaskResult, **kwargs: Any) -> None: ... diff --git a/django-stubs/utils/connection.pyi b/django-stubs/utils/connection.pyi index 058c5ac00..56ddf64fb 100644 --- a/django-stubs/utils/connection.pyi +++ b/django-stubs/utils/connection.pyi @@ -1,36 +1,37 @@ -from abc import abstractmethod -from collections.abc import Iterator -from typing import Any +from collections.abc import Iterator, Sequence +from typing import Any, Generic -from django.db.backends.base.base import BaseDatabaseWrapper +from django.db.models.query import _SupportsContains from django.utils.functional import cached_property +from typing_extensions import TypeVar, override -class ConnectionProxy: - """Proxy for accessing a connection object's attributes.""" +_T = TypeVar("_T") - def __init__(self, connections: BaseConnectionHandler, alias: str) -> None: ... +class ConnectionProxy(Generic[_T]): + def __init__(self, connections: BaseConnectionHandler[_T], alias: str) -> None: ... def __getattr__(self, item: str) -> Any: ... + @override def __setattr__(self, name: str, value: Any) -> None: ... + @override def __delattr__(self, name: str) -> None: ... def __contains__(self, key: str) -> bool: ... + @override def __eq__(self, other: object) -> bool: ... class ConnectionDoesNotExist(Exception): ... -class BaseConnectionHandler: +class BaseConnectionHandler(_SupportsContains[str], Generic[_T]): settings_name: str | None exception_class: type[Exception] thread_critical: bool - - def __init__(self, settings: dict[str, dict[str, Any]] | None = None) -> None: ... @cached_property - def settings(self) -> dict[str, dict[str, Any]]: ... - def configure_settings(self, settings: dict[str, dict[str, Any]] | None) -> dict[str, dict[str, Any]]: ... - @abstractmethod - def create_connection(self, alias: str) -> BaseDatabaseWrapper: ... - def __getitem__(self, alias: str) -> BaseDatabaseWrapper: ... - def __setitem__(self, key: str, value: BaseDatabaseWrapper) -> None: ... + def settings(self) -> dict[str, Any]: ... + def __init__(self, settings: Any | None = None) -> None: ... + def configure_settings(self, settings: dict[str, Any] | None) -> dict[str, Any]: ... + def create_connection(self, alias: str) -> _T: ... + def __getitem__(self, alias: str) -> _T: ... + def __setitem__(self, key: str, value: _T) -> None: ... def __delitem__(self, key: str) -> None: ... def __iter__(self) -> Iterator[str]: ... - def all(self, initialized_only: bool = False) -> list[BaseDatabaseWrapper]: ... + def all(self, initialized_only: bool = False) -> Sequence[_T]: ... def close_all(self) -> None: ... diff --git a/s/sync b/s/sync index 3edce2850..11301433a 100755 --- a/s/sync +++ b/s/sync @@ -6,6 +6,10 @@ set -e # Reads files to sync from s/sync-files.txt (one file per line) # Usage: uv run s/sync +DJANGO_STUBS_TARGET_TAG="6.0.5" +git fetch https://github.com/typeddjango/django-stubs.git $DJANGO_STUBS_TARGET_TAG +git checkout $DJANGO_STUBS_TARGET_TAG -- django-stubs/tasks/ + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SYNC_FILES="$SCRIPT_DIR/sync-files.txt" UPSTREAM_BASE_URL="https://raw.githubusercontent.com/typeddjango/django-stubs/master"