From 10a6d037f0e3fa71839a4ff23b6550abcd370d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Thu, 6 Aug 2026 19:45:48 +0200 Subject: [PATCH 01/10] chore(deps): usun django-prometheus, udokumentuj mapowanie wersji Grappelli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Przygotowanie pod Django 6.1. django-prometheus 2.5.0 ma twardy cap `Django<6.1`. Integracja byla w calosci opcjonalna (flaga DJANGO_BPP_ENABLE_PROMETHEUS, domyslnie False), wiec usuwamy pakiet wraz z obsluga flagi, middleware, wpisem w INSTALLED_APPS i routingiem /metrics. Grappelli nie wymaga zmiany wersji — 5.0.0 jest juz linia dla Django 6.x. Dodany komentarz przy pinie, bo numeracja Grappelli jest przesunieta o jeden wstecz wzgledem Django (5.0.x -> Django 6.x, 4.0.x -> Django 5.x) i dependabot juz raz podbil to w zla strone (29f0ecd7b), zostawiajac skorke admina dla Django 6.x na Django 5.2. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F134BWb3YoYPQ6zjzqoqds --- pyproject.toml | 6 ++++- .../django-prometheus.removal.rst | 4 +++ src/django_bpp/settings/base.py | 25 ------------------- src/django_bpp/urls.py | 6 ----- uv.lock | 24 ------------------ 5 files changed, 9 insertions(+), 56 deletions(-) create mode 100644 src/bpp/newsfragments/django-prometheus.removal.rst diff --git a/pyproject.toml b/pyproject.toml index cf2385a7e..ed0515f43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,11 @@ dependencies = [ "django-extensions>=3.2,<5", "django-minify-html>=1.14,<2", "Pillow>=12.2,<14", + # UWAGA: linia wersji Grappelli jest przesunieta o jeden WSTECZ wzgledem + # Django — Grappelli 5.0.x jest dla Django 6.x, a 4.0.x dla Django 5.x. + # Nie "podbijaj" tego automatem bez sprawdzenia, na ktorym Django stoimy; + # dependabot juz raz przeskoczyl 4.0.3 -> 5.0.0 (29f0ecd7b) zostawiajac + # skorke admina dla Django 6.x na Django 5.2. "django-grappelli>=5.0.0,<6", "django-multiseek==0.10.2", "django-braces>=1.15.0", @@ -135,7 +140,6 @@ dependencies = [ "thefuzz>=0.22.1", "celery-singleton>=0.3.1", "rollbar>=1.4.0", - "django-prometheus>=2.5.0", "ortools>=9.15.6755", "psycopg2-binary>=2.9.12", # ZOSTAW psycopg2 dla django-denorm "python-docx>=1.1.0", # Post-processing DOCX dla page breaks diff --git a/src/bpp/newsfragments/django-prometheus.removal.rst b/src/bpp/newsfragments/django-prometheus.removal.rst new file mode 100644 index 000000000..c5226b00f --- /dev/null +++ b/src/bpp/newsfragments/django-prometheus.removal.rst @@ -0,0 +1,4 @@ +Usunięto opcjonalną integrację ze statystykami Prometheus (pakiet +``django-prometheus`` oraz zmienna środowiskowa +``DJANGO_BPP_ENABLE_PROMETHEUS``). Integracja była domyślnie wyłączona, +a pakiet blokował aktualizację do Django 6.1. diff --git a/src/django_bpp/settings/base.py b/src/django_bpp/settings/base.py index 2168e7654..0f96a595e 100644 --- a/src/django_bpp/settings/base.py +++ b/src/django_bpp/settings/base.py @@ -168,10 +168,6 @@ def int_or_none(v): # stronie, której nie naprawimy kodem. DJANGO_BPP_ROLLBAR_IGNORE_SMTP_AUTH_ERRORS=(bool, False), # - # Prometheus - # - DJANGO_BPP_ENABLE_PROMETHEUS=(bool, False), - # # Liczniki w filtrac # DYNAMIC_FILTER_COUNTS_ENABLE=(bool, True), @@ -1802,27 +1798,6 @@ def iter_namespace(ns_pkg): # bezpiecznie renderować w przeglądarce. Pusty = front-end Rollbar wyłączony. ROLLBAR_CLIENT_ACCESS_TOKEN = env("ROLLBAR_CLIENT_ACCESS_TOKEN") -# -# Prometheus -# - -DJANGO_BPP_ENABLE_PROMETHEUS = env("DJANGO_BPP_ENABLE_PROMETHEUS") - -if DJANGO_BPP_ENABLE_PROMETHEUS: - MIDDLEWARE = ( - [ - "django_prometheus.middleware.PrometheusBeforeMiddleware", - ] - + MIDDLEWARE - + [ - "django_prometheus.middleware.PrometheusAfterMiddleware", - ] - ) - - INSTALLED_APPS += [ - "django_prometheus", - ] - DYNAMIC_FILTER_COUNTS_ENABLE = env("DYNAMIC_FILTER_COUNTS_ENABLE") # ============================================================================= diff --git a/src/django_bpp/urls.py b/src/django_bpp/urls.py index 20768cd39..63cb04b8a 100644 --- a/src/django_bpp/urls.py +++ b/src/django_bpp/urls.py @@ -439,12 +439,6 @@ def protected_media_serve(request, path, document_root=None): ] -if settings.DJANGO_BPP_ENABLE_PROMETHEUS: - urlpatterns += [ - path("", include("django_prometheus.urls")), - ] - - if settings.DEBUG and settings.DEBUG_TOOLBAR: from debug_toolbar.toolbar import debug_toolbar_urls diff --git a/uv.lock b/uv.lock index 928c94846..769d987b9 100644 --- a/uv.lock +++ b/uv.lock @@ -432,7 +432,6 @@ dependencies = [ { name = "django-pbn-client", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-pg-baseline", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-polish-inflection", marker = "platform_python_implementation != 'PyPy'" }, - { name = "django-prometheus", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-querysetsequence", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-redis-iplweb", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-reversion", marker = "platform_python_implementation != 'PyPy'" }, @@ -619,7 +618,6 @@ requires-dist = [ { name = "django-pbn-client", git = "https://github.com/iplweb/django-pbn-client.git?rev=bb952e98967a96f96cd3773e3dcfcaf4555c8690" }, { name = "django-pg-baseline", specifier = ">=0.3.0" }, { name = "django-polish-inflection", specifier = ">=0.1,<0.2" }, - { name = "django-prometheus", specifier = ">=2.5.0" }, { name = "django-querysetsequence", specifier = "==0.18" }, { name = "django-redis-iplweb", specifier = ">=6.0.1,<6.1" }, { name = "django-reversion", specifier = ">=6.3.0,<7" }, @@ -2139,19 +2137,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/c6/31368656d2c02cacbc0f6008d520abddce1cd3619970c806da74bc6fbb99/django_polish_inflection-0.1.0-py3-none-any.whl", hash = "sha256:b968f6d8f6beedc00900d7b88fd760fba83c76d62b593961e56b743e6596f96c", size = 9153, upload-time = "2026-07-03T20:04:12.004Z" }, ] -[[package]] -name = "django-prometheus" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django", marker = "platform_python_implementation != 'PyPy'" }, - { name = "prometheus-client", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7b/c7/dc39c4c19f7b35e827a486d08376de1fad31c50decb26c56e32668314f13/django_prometheus-2.5.0.tar.gz", hash = "sha256:4837b3c3734d8350880839ab8235aafd250b668c348e159d4aecc3cbefeee53e", size = 26465, upload-time = "2026-05-26T19:04:00.77Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/5d/6ec3083ba69545696c962ae505a0e52e280e7592d4c278c2f3803cabb688/django_prometheus-2.5.0-py2.py3-none-any.whl", hash = "sha256:f15efb526cd53f9cf12da72dc55506322f5566b017a819ff27be1da302303134", size = 31801, upload-time = "2026-05-26T19:03:59.505Z" }, -] - [[package]] name = "django-querysetsequence" version = "0.18" @@ -4181,15 +4166,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/96/2f4b8da7be255cd41e825c398efd11a6706ff86e66ae198f012204aa2a4f/priority-1.3.0-py2.py3-none-any.whl", hash = "sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb", size = 11720, upload-time = "2017-01-27T11:00:52.07Z" }, ] -[[package]] -name = "prometheus-client" -version = "0.23.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, -] - [[package]] name = "prompt-toolkit" version = "3.0.52" From cac6fd531b56403ffaa5721913951c3350f1f670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Thu, 6 Aug 2026 22:45:27 +0200 Subject: [PATCH 02/10] feat: aktualizacja do Django 6.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Podnosi Django z 5.2.16 LTS do 6.1 i `requires-python` do >=3.12 (wymaganie 6.1; obraz Dockera i glowne CI juz stoja na 3.12). Zaleznosci: * `django-redis-iplweb` -> upstreamowy `django-redis` 7.x. Fork byl czystym republishem (zero kodu iplweb, ten sam modul `django_redis`) i utknal na `Django<6.1`. Nazwa backendu w konfiguracji sie NIE zmienia, wiec wdrozenia nie wymagaja akcji. * `djangorestframework` i `django-pbn-client` TYMCZASOWO z gita — oba maja fix na glownej galezi, ale nie na PyPI. Warunki wyjscia opisane przy pinach w [tool.uv.sources]. Naprawione regresje 6.1 (kazda potwierdzona kontrola: przechodzi na 5.2, oblewa na 6.1 przy niezmienionym otoczeniu): * `ForwardManyToOneDescriptor.get_queryset()` wymaga teraz keyword-only `instance` (dokłada `.fetch_mode()` z jej stanu). Wolane bylo w czasie definicji klasy formularza — zastapione siegnieciem po `_base_manager` wprost, czyli tym, co deskryptor robil pod spodem. * Aliasy SELECT-a sa konsekwentnie cytowane, przez co odziedziczone `Meta.ordering` w zapytaniu zlozonym kompilowalo sie do `ORDER BY "col2"` -> ProgrammingError. Puste `.order_by()` na operandach `union()`, na jego wyniku i na wyniku `difference()`. * `BaseModelForm.validate_constraints()` to metoda NOWA w 6.1 (w 5.2 nie istniala). Psula podmiane kierownika projektu w jednym zapisie: wiersz oznaczony DELETE wciaz jest w bazie. Django koordynuje walidacje miedzy formularzami tylko dla unikalnosci; dla constraintow odpowiednika nie ma. * `Script.__str__` renderuje teraz przez staticfiles storage, wiec URL zawiera content-hash — asercja w tescie toleruje go zamiast porownywac literal. Dodatkowo: shim `django.utils.itercompat` dla `django-admin-tools` 0.9.3 (martwy import usunietego modulu), usuniete martwe `FORMS_URLFIELD_ASSUME_HTTPS` wraz z jego wyciszaczem w pytest.ini. Weryfikacja: 9308 zdanych, 0 porazek (bez Playwrighta). `manage.py check` czysto. Dryf `makemigrations --check` identyczny jak na 5.2 (stan zastany). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F134BWb3YoYPQ6zjzqoqds --- pyproject.toml | 35 +- pytest.ini | 3 - src/bpp/admin/core.py | 10 +- src/bpp/admin/projekt.py | 27 + src/bpp/newsfragments/django-61.feature.rst | 5 + src/bpp/tests/test_zapytanie.py | 23 +- src/django_bpp/compat.py | 60 +++ src/django_bpp/settings/base.py | 8 +- src/raport_slotow/core.py | 30 +- uv.lock | 566 +------------------- 10 files changed, 201 insertions(+), 566 deletions(-) create mode 100644 src/bpp/newsfragments/django-61.feature.rst create mode 100644 src/django_bpp/compat.py diff --git a/pyproject.toml b/pyproject.toml index ed0515f43..58f931dbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,9 +28,10 @@ authors = [ classifiers = [ "DoNotUpload :: Use this project via github.com/iplweb/bpp-deploy repo", ] -requires-python = ">=3.11,<3.15" +# Django 6.1 wymaga Pythona >= 3.12 (3.11 wypadlo razem z 6.0). +requires-python = ">=3.12,<3.15" dependencies = [ - "Django>=5.2.16,<5.3", + "Django>=6.1,<6.2", # Reużywalne pakiety PBN wydzielone na PyPI (github.com/iplweb/*). "django-pbn-client>=0.2,<0.3", "pbn-client>=0.2.2,<0.3", @@ -100,10 +101,16 @@ dependencies = [ "django-mptt>=0.16,<1", "wosclient==0.1.5", "MOAI-iplweb>=2.0.2", - "django_redis_iplweb>=6.0.1,<6.1", + # Powrot na upstream jazzband/django-redis: fork `django_redis_iplweb` + # byl czystym republishem (zero kodu iplweb, ten sam modul `django_redis`) + # i utknal na `Django<6.1`. Upstream 7.0.0 deklaruje `Django<7.0,>=5.2`, + # wiec obejmuje 6.1. Nazwa modulu w settings sie NIE zmienia. + "django-redis>=7,<8", "django-filter>=26.1,<26.2", "dbfread>=2.0.7", - "djangorestframework==3.17.1", + # TYMCZASOWO z gita — patrz [tool.uv.sources] nizej. DRF <=3.17.2 + # importuje `cc_delim_re` z django.utils.cache, usuniete w Django 6.1. + "djangorestframework>=3.17.2", "Markdown>=3.7,<4", "django-admin-sortable2>=2.3.1,<3", "cssmin==0.2.0", @@ -241,7 +248,7 @@ tag = false push = false [tool.uv] -environments = ["python_version >= '3.10' and python_version < '3.15' and platform_python_implementation != 'PyPy'"] +environments = ["python_version >= '3.12' and python_version < '3.15' and platform_python_implementation != 'PyPy'"] # Security floors dla zaleznosci TRANZYTYWNYCH (nie deklarujemy ich jako # direct deps - przychodza przez inne pakiety). constraint-dependencies @@ -320,10 +327,20 @@ default = true # WYCOFAĆ gdy upstream wyda release z #2156 → wróć na PyPI `>=4.5`. Tracking: #280. [tool.uv.sources] django-import-export = { git = "https://github.com/mpasternak/django-import-export.git", rev = "d6ee0d39194fee31437affbdc6fee5ce549b4b8f" } -# TYMCZASOWO: django-pbn-client 0.2.2 (sync_dictionary D3 + with_deleted_marker -# A2) nie jest jeszcze na PyPI. Pin do commita gałęzi feat/deleted-marker -# (stacked na feat/sync-dictionary). USUŃ po wydaniu 0.2.2 na PyPI. -django-pbn-client = { git = "https://github.com/iplweb/django-pbn-client.git", rev = "bb952e98967a96f96cd3773e3dcfcaf4555c8690" } +# TYMCZASOWO: django-pbn-client na PyPI (0.2.2) ma twardy cap `django<5.3`, +# ktory blokuje Django 6.1. Pin do HEAD gałęzi feat/django-61 (PR +# iplweb/django-pbn-client#5 — luzuje pin na `Django>=5.2` i dokłada oś +# django-version 5.2/6.0/6.1 do matrycy CI). +# USUŃ i wróć na `django-pbn-client>=0.3` gdy PR #5 zostanie zmergowany +# i wydany na PyPI. +django-pbn-client = { git = "https://github.com/iplweb/django-pbn-client.git", rev = "1b91ac4f968a971815160863cc7297a17dc8712b" } +# TYMCZASOWO: DRF 3.17.2 (najnowsze na PyPI, wydane 2026-08-05) wciaz robi +# `from django.utils.cache import cc_delim_re`, a Django 6.1 to usunelo -> +# ImportError przy imporcie rest_framework.views, czyli calego api_v1. +# Na `main` fix jest juz w drzewie (import zwezony do patch_vary_headers), +# a PR encode/django-rest-framework#10015 przygotowuje wydanie 3.18.0. +# USUŃ i wróć na `djangorestframework>=3.18` gdy 3.18.0 trafi na PyPI. +djangorestframework = { git = "https://github.com/encode/django-rest-framework.git", rev = "80d535ddf08226020a9f44ca57f6eb1bbd91fa6a" } # Konfiguracja `pytest-testcontainers-django` — pluginu pytest, ktory # startuje kontenery PG/Redis przed importem Django settings i wstrzykuje diff --git a/pytest.ini b/pytest.ini index 64e0510e2..48cccbbc7 100644 --- a/pytest.ini +++ b/pytest.ini @@ -51,9 +51,6 @@ filterwarnings = ignore:.*django-fsm.*viewflow.*:UserWarning:django_fsm # pbn_api test data often lacks current_version; not actionable: ignore:.*has NO current_version.*:UserWarning - # FORMS_URLFIELD_ASSUME_HTTPS is an intentional opt-in for Django 6.0 - # behaviour; to be removed together with the setting on 6.0 upgrade. - ignore:.*FORMS_URLFIELD_ASSUME_HTTPS.*:DeprecationWarning # pyoai 2.5.0 (infrae/pyoai) nadal woła datetime.utcnow() # w oaipmh/server.py; nie mamy forku — zgłoszenie upstream. ignore:.*datetime\.datetime\.utcnow.*:DeprecationWarning:oaipmh\.server diff --git a/src/bpp/admin/core.py b/src/bpp/admin/core.py index 78330aaa2..3ceef8e41 100644 --- a/src/bpp/admin/core.py +++ b/src/bpp/admin/core.py @@ -148,8 +148,16 @@ def generuj_formularz_dla_autorow( # noqa ): class baseModel_AutorForm(forms.ModelForm): if include_rekord: + # Django 6.1: ForwardManyToOneDescriptor.get_queryset() wymaga + # keyword-only `instance` (dokłada .fetch_mode() z jej stanu), a + # tutaj jesteśmy w czasie definicji klasy — żadnej instancji nie + # ma. Sięgamy po `_base_manager` wprost, czyli dokładnie to, co + # deskryptor robił pod spodem w 5.2. `_base_manager`, NIE + # `_default_manager` — to pole ma być w stanie dowiązać rekord + # odfiltrowany przez managera domyślnego (m.in. soft-delete). rekord = forms.ModelChoiceField( - widget=HiddenInput, queryset=baseModel.rekord.get_queryset() + widget=HiddenInput, + queryset=baseModel.rekord.field.remote_field.model._base_manager.all(), ) autor = forms.ModelChoiceField( diff --git a/src/bpp/admin/projekt.py b/src/bpp/admin/projekt.py index 92fa881cf..bf015ec66 100644 --- a/src/bpp/admin/projekt.py +++ b/src/bpp/admin/projekt.py @@ -21,6 +21,32 @@ from .helpers.mixins import ZapiszZAdnotacjaMixin +class Projekt_AutorForm(forms.ModelForm): + """Wyłącza walidację ``UniqueConstraint`` na poziomie pojedynczego wiersza. + + Django 6.1 dodało ``BaseModelForm.validate_constraints()`` (w 5.2 ta + metoda jeszcze nie istniała) — każdy formularz sam odpytuje bazę o + naruszenie constraintów modelu. W inline'ie to daje fałszywy alarm przy + podmianie kierownika w jednym zapisie: stary wiersz jest oznaczony + ``DELETE``, ale wciąż siedzi w bazie, więc nowy kierownik „narusza" + ``projekt_jeden_kierownik``, choć po zapisie stan będzie poprawny. + + Django koordynuje to między formularzami tylko dla unikalności — + ``BaseModelFormSet.validate_unique()`` odsiewa ``self.deleted_forms``. + Dla constraintów odpowiednika nie ma, więc odsiew robimy sami: + ``Projekt_AutorFormSet.clean()`` niżej liczy kierowników pomijając wiersze + z ``DELETE``, a constraint w bazie zostaje jako ostatnia linia obrony. + Netto: zachowanie sprzed 6.1, tyle że teraz świadomie. + """ + + def clean(self): + dane = super().clean() + # ``BaseModelForm.clean()`` ustawia tę flagę na True; gasimy ją po + # nim, ale przed ``_post_clean()``, które ją odczytuje. + self._validate_constraints = False + return dane + + class Projekt_AutorFormSet(forms.BaseInlineFormSet): """Duplikuje bazowy ``UniqueConstraint`` na „jeden kierownik na projekt”. @@ -46,6 +72,7 @@ def clean(self): class Projekt_AutorInline(admin.TabularInline): model = Projekt_Autor + form = Projekt_AutorForm formset = Projekt_AutorFormSet extra = 0 autocomplete_fields = ["autor"] diff --git a/src/bpp/newsfragments/django-61.feature.rst b/src/bpp/newsfragments/django-61.feature.rst new file mode 100644 index 000000000..e940ff026 --- /dev/null +++ b/src/bpp/newsfragments/django-61.feature.rst @@ -0,0 +1,5 @@ +Aktualizacja do Django 6.1. Minimalna wersja Pythona to teraz 3.12 +(wymaganie Django 6.1). Backend cache'u wrócił z forku +``django-redis-iplweb`` na upstreamowy ``django-redis`` — nazwa modułu +w konfiguracji (``django_redis.cache.RedisCache``) się nie zmienia, +więc wdrożenia nie wymagają zmian. diff --git a/src/bpp/tests/test_zapytanie.py b/src/bpp/tests/test_zapytanie.py index b700e92d6..c5f645bce 100644 --- a/src/bpp/tests/test_zapytanie.py +++ b/src/bpp/tests/test_zapytanie.py @@ -1,3 +1,5 @@ +import re + import pytest from django.contrib.auth.models import Group from django.urls import reverse @@ -687,6 +689,23 @@ def test_wykonaj_zapytanie_dedupuje_po_relacji_do_wielu( assert wynik.queryset.count() == 1 +def _laduje_skrypt(js, nazwa_pliku): + """Czy lista mediów admina ładuje plik JS o tej nazwie? + + Django 6.1 renderuje ``str(Script(...))`` przez staticfiles storage, więc + przy ``ManifestStaticFilesStorage`` w URL-u ląduje content-hash + (``highlight.js`` → ``highlight.f65f9cbe99ea.js``). Do 6.0 ``str()`` + zwracał ścieżkę bez hasha, dlatego gołe ``"highlight.js" in str(p)`` + działało. Dopasowujemy nazwę tolerując wstawiony hash — sprawdzamy to, + co realnie trafia do strony, ale bez przywiązania do cache-bustingu. + """ + trzon, _, rozszerzenie = nazwa_pliku.rpartition(".") + wzorzec = re.compile( + rf"{re.escape(trzon)}(\.[0-9a-f]{{8,}})?\.{re.escape(rozszerzenie)}" + ) + return any(wzorzec.search(str(p)) for p in js) + + def test_admin_djangoql_highlight_loaded(): """Adminy z BppDjangoQLSearchMixin ładują nakładkę podświetlania + skrypt falki błędu (przez własne ``media``, nie wbudowane ``djangoql_highlight``).""" @@ -703,8 +722,8 @@ def test_admin_djangoql_highlight_loaded(): (Wydawnictwo_CiagleAdmin, Wydawnictwo_Ciagle), ): js = list(admin_cls(model, dj_admin.site).media._js) - assert any("highlight.js" in str(p) for p in js), (admin_cls.__name__, js) - assert any("djangoql-admin.js" in str(p) for p in js), ( + assert _laduje_skrypt(js, "highlight.js"), (admin_cls.__name__, js) + assert _laduje_skrypt(js, "djangoql-admin.js"), ( admin_cls.__name__, js, ) diff --git a/src/django_bpp/compat.py b/src/django_bpp/compat.py new file mode 100644 index 000000000..409d77ac4 --- /dev/null +++ b/src/django_bpp/compat.py @@ -0,0 +1,60 @@ +"""Łatki zgodnościowe dla nieutrzymywanych zależności third-party. + +Importowany z ``django_bpp.settings.base`` — a więc ZANIM +``django.setup()`` wywoła ``apps.populate()`` i zacznie importować +moduły z ``INSTALLED_APPS``. To jedyne okno, w którym da się wstawić +brakujący moduł do ``sys.modules`` tak, żeby zdążył przed importem +pakietu, który go szuka. + +Każda łatka tutaj to dług techniczny z jawnym warunkiem wyjścia. +Nie dokładaj kolejnych bez opisania, co ma się wydarzyć, żeby +można ją było skasować. +""" + +import sys +import types + + +def _zalataj_itercompat(): + """Przywróć ``django.utils.itercompat`` dla ``django-admin-tools``. + + ``django-admin-tools`` 0.9.3 (ostatnie wydanie, klasyfikatory kończą + się na Django 4.0) ma w ``admin_tools/dashboard/modules.py``:: + + from django.utils.itercompat import is_iterable + + Django 6.1 usunęło ten moduł, więc import wywala się na starcie i + kładzie całe ``apps.populate()``. + + Import jest **martwy** — ``is_iterable`` nie jest używane nigdzie w + ``admin_tools`` (sprawdzone grepem po całym pakiecie). Wystarczy więc + dostarczyć nazwę; żaden kod jej nie zawoła. Implementacja i tak jest + wierna oryginałowi z Django (``iter()`` + ``TypeError``), żeby łatka + nie kłamała, gdyby kiedyś jednak została użyta. + + WARUNEK WYJŚCIA: skasować razem z całym ``django-admin-tools``, gdy + BPP przejdzie na inny mechanizm dashboardu/menu, albo gdy powstanie + fork ``django-admin-tools-iplweb`` z usuniętą tą linią. Sam upstream + jest praktycznie martwy (0.9.3 to ostatnie wydanie), więc czekanie na + poprawkę od niego nie jest realnym planem. + """ + if "django.utils.itercompat" in sys.modules: + return + + modul = types.ModuleType("django.utils.itercompat") + modul.__doc__ = "Shim BPP dla django-admin-tools — patrz django_bpp.compat." + + def is_iterable(x): + try: + iter(x) + except TypeError: + return False + return True + + modul.is_iterable = is_iterable + sys.modules["django.utils.itercompat"] = modul + + +def zalataj_zaleznosci(): + """Nałóż wszystkie łatki zgodnościowe. Idempotentne.""" + _zalataj_itercompat() diff --git a/src/django_bpp/settings/base.py b/src/django_bpp/settings/base.py index 0f96a595e..00dcb1f09 100644 --- a/src/django_bpp/settings/base.py +++ b/src/django_bpp/settings/base.py @@ -15,9 +15,13 @@ from bpp.util import slugify_function from django_bpp.channels_prefix import get_channels_prefix +from django_bpp.compat import zalataj_zaleznosci from django_bpp.rollbar_filters import zbuduj_exception_level_filters from django_bpp.version import VERSION +# MUSI pójść przed apps.populate() — patrz django_bpp.compat. +zalataj_zaleznosci() + logger = logging.getLogger(__name__) SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__)) @@ -228,10 +232,6 @@ def int_or_none(v): # nowe_raporty.docx_export._convert_using_html2docx_service. HTML2DOCX_URL = env("DJANGO_BPP_HTML2DOCX_URL", default=None) -# Django 5.0 transitional; stanie się domyślne w 6.0. Wycisza -# RemovedInDjango60Warning z forms.URLField dla URL-i bez schematu. -FORMS_URLFIELD_ASSUME_HTTPS = True - STATIC_URL = "/static/" ADMIN_MEDIA_PREFIX = "/static/admin/" diff --git a/src/raport_slotow/core.py b/src/raport_slotow/core.py index c45dbe557..39a855c70 100644 --- a/src/raport_slotow/core.py +++ b/src/raport_slotow/core.py @@ -21,17 +21,34 @@ def autorzy_z_dyscyplinami(od_roku=None, do_roku=None) -> list[tuple[int, int, i kwargs = _get_kwargs(od_roku, do_roku) + # Puste ``.order_by()`` w TRZECH miejscach — na obu operandach UNION-a + # ORAZ na jego wyniku. To nie jest nadmiarowe: operandy wnoszą + # ``Meta.ordering`` do składowych SELECT-ów, a wynik UNION-a dziedziczy je + # jeszcze raz, jako sortowanie całości. + # + # Do Django 6.0 uchodziło to płazem. Od 6.1 aliasy SELECT-a są + # konsekwentnie cytowane, więc odziedziczone ``ORDER BY rok`` kompiluje się + # do ``ORDER BY "col2"`` (``rok`` to druga kolumna w ``values()``) — + # aliasu, którego złożony SELECT nie wystawia. Efekt: + # ``ProgrammingError: kolumna "col2" nie istnieje``. + # + # Sortowanie i tak nie miało tu znaczenia: wynik trafia do + # ``difference()`` w ``autorzy_zerowi()`` i dalej jest konsumowany jako + # zbiór krotek. return ( Autor_Dyscyplina.objects.values("autor_id", "rok", "dyscyplina_naukowa_id") .filter(**kwargs) .exclude(dyscyplina_naukowa_id=None) + .order_by() .union( Autor_Dyscyplina.objects.values( "autor_id", "rok", "subdyscyplina_naukowa_id" ) .exclude(subdyscyplina_naukowa_id=None) .filter(**kwargs) + .order_by() ) + .order_by() ) @@ -59,7 +76,11 @@ def autorzy_z_punktami( if uczelnia is not None: qs = qs.filter(uczelnia=uczelnia) - return qs.values("autor_id", "rekord__rok", "dyscyplina_id") + # ``.order_by()`` — jak wyżej. ``Meta.ordering`` tego modelu to + # ``rekord__tytul_oryginalny``/``dyscyplina__nazwa``, czyli kolumny spoza + # ``values()`` i zza JOIN-a; w ``difference()`` (patrz ``autorzy_zerowi``) + # to właśnie one wsadzały ORDER BY do składnika złożonego zapytania. + return qs.values("autor_id", "rekord__rok", "dyscyplina_id").order_by() def autorzy_zerowi(od_roku=None, do_roku=None, min_pk=None, uczelnia=None): @@ -81,4 +102,9 @@ def autorzy_zerowi(od_roku=None, do_roku=None, min_pk=None, uczelnia=None): od_roku=od_roku, do_roku=do_roku, min_pk=min_pk, uczelnia=uczelnia ) - return defined.difference(existent) + # Trzecie miejsce z pustym ``.order_by()`` (patrz komentarz w + # ``autorzy_z_dyscyplinami``): wynik ``difference()`` też niesie flagę + # domyślnego sortowania modelu, a konsumenci wołają na nim ``values_list()`` + # (``raport_slotow/models/uczelnia.py``), co klonuje zapytanie razem z tą + # flagą — i ``Meta.ordering`` wraca przy kompilacji jako ``ORDER BY "col2"``. + return defined.difference(existent).order_by() diff --git a/uv.lock b/uv.lock index 769d987b9..82b12f86f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,19 +1,16 @@ version = 1 revision = 3 -requires-python = ">=3.11, <3.15" +requires-python = ">=3.12, <3.15" resolution-markers = [ "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", + "python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", "python_full_version == '3.13.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'", + "python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version < '3.12' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", - "python_full_version < '3.12' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] supported-markers = [ "platform_python_implementation != 'PyPy'", @@ -120,15 +117,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, ] -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, -] - [[package]] name = "attrs" version = "25.4.0" @@ -208,15 +196,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] -[[package]] -name = "backports-tarfile" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, -] - [[package]] name = "bcrypt" version = "5.0.0" @@ -281,10 +260,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, - { url = "https://files.pythonhosted.org/packages/8a/75/4aa9f5a4d40d762892066ba1046000b329c7cd58e888a6db878019b282dc/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534", size = 271180, upload-time = "2025-09-25T19:50:38.575Z" }, - { url = "https://files.pythonhosted.org/packages/54/79/875f9558179573d40a9cc743038ac2bf67dfb79cecb1e8b5d70e88c94c3d/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4", size = 273791, upload-time = "2025-09-25T19:50:39.913Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fe/975adb8c216174bf70fc17535f75e85ac06ed5252ea077be10d9cff5ce24/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911", size = 270746, upload-time = "2025-09-25T19:50:43.306Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f8/972c96f5a2b6c4b3deca57009d93e946bbdbe2241dca9806d502f29dd3ee/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4", size = 273375, upload-time = "2025-09-25T19:50:45.43Z" }, ] [[package]] @@ -433,7 +408,7 @@ dependencies = [ { name = "django-pg-baseline", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-polish-inflection", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-querysetsequence", marker = "platform_python_implementation != 'PyPy'" }, - { name = "django-redis-iplweb", marker = "platform_python_implementation != 'PyPy'" }, + { name = "django-redis", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-reversion", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-reversion-compare", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-sendfile2", marker = "platform_python_implementation != 'PyPy'" }, @@ -567,7 +542,7 @@ requires-dist = [ { name = "cssmin", specifier = "==0.2.0" }, { name = "dbfread", specifier = ">=2.0.7" }, { name = "dj-pagination", specifier = "==2.5.0" }, - { name = "django", specifier = ">=5.2.16,<5.3" }, + { name = "django", specifier = ">=6.1,<6.2" }, { name = "django-admin-sortable2", specifier = ">=2.3.1,<3" }, { name = "django-admin-tools", specifier = "==0.9.3" }, { name = "django-altcha", specifier = ">=1.0.0" }, @@ -615,11 +590,11 @@ requires-dist = [ { name = "django-multiseek", specifier = "==0.10.2" }, { name = "django-oauth-toolkit", specifier = "==3.4.0" }, { name = "django-password-policies-iplweb", specifier = "==0.9.0" }, - { name = "django-pbn-client", git = "https://github.com/iplweb/django-pbn-client.git?rev=bb952e98967a96f96cd3773e3dcfcaf4555c8690" }, + { name = "django-pbn-client", git = "https://github.com/iplweb/django-pbn-client.git?rev=1b91ac4f968a971815160863cc7297a17dc8712b" }, { name = "django-pg-baseline", specifier = ">=0.3.0" }, { name = "django-polish-inflection", specifier = ">=0.1,<0.2" }, { name = "django-querysetsequence", specifier = "==0.18" }, - { name = "django-redis-iplweb", specifier = ">=6.0.1,<6.1" }, + { name = "django-redis", specifier = ">=7,<8" }, { name = "django-reversion", specifier = ">=6.3.0,<7" }, { name = "django-reversion-compare", specifier = ">=0.19.2" }, { name = "django-sendfile2", specifier = ">=0.7.2" }, @@ -635,7 +610,7 @@ requires-dist = [ { name = "django-weasyprint", specifier = ">=2.5.0" }, { name = "django-webmaster-verification", specifier = "==0.4.3" }, { name = "djangoql-iplweb", specifier = ">=0.31.1" }, - { name = "djangorestframework", specifier = "==3.17.1" }, + { name = "djangorestframework", git = "https://github.com/encode/django-rest-framework.git?rev=80d535ddf08226020a9f44ca57f6eb1bbd91fa6a" }, { name = "dspace-rest-client", specifier = ">=0.1.12" }, { name = "gunicorn", specifier = ">=26.0.0" }, { name = "isbnlib", specifier = ">=3.10.14" }, @@ -731,16 +706,6 @@ version = "1.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/ef/f285668811a9e1ddb47a18cb0b437d5fc2760d537a2fe8a57875ad6f8448/brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744", size = 863110, upload-time = "2025-11-05T18:38:12.978Z" }, - { url = "https://files.pythonhosted.org/packages/50/62/a3b77593587010c789a9d6eaa527c79e0848b7b860402cc64bc0bc28a86c/brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f", size = 445438, upload-time = "2025-11-05T18:38:14.208Z" }, - { url = "https://files.pythonhosted.org/packages/cd/e1/7fadd47f40ce5549dc44493877db40292277db373da5053aff181656e16e/brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd", size = 1534420, upload-time = "2025-11-05T18:38:15.111Z" }, - { url = "https://files.pythonhosted.org/packages/12/8b/1ed2f64054a5a008a4ccd2f271dbba7a5fb1a3067a99f5ceadedd4c1d5a7/brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe", size = 1632619, upload-time = "2025-11-05T18:38:16.094Z" }, - { url = "https://files.pythonhosted.org/packages/89/5a/7071a621eb2d052d64efd5da2ef55ecdac7c3b0c6e4f9d519e9c66d987ef/brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a", size = 1426014, upload-time = "2025-11-05T18:38:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/26/6d/0971a8ea435af5156acaaccec1a505f981c9c80227633851f2810abd252a/brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b", size = 1489661, upload-time = "2025-11-05T18:38:18.41Z" }, - { url = "https://files.pythonhosted.org/packages/f3/75/c1baca8b4ec6c96a03ef8230fab2a785e35297632f402ebb1e78a1e39116/brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3", size = 1599150, upload-time = "2025-11-05T18:38:19.792Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1a/23fcfee1c324fd48a63d7ebf4bac3a4115bdb1b00e600f80f727d850b1ae/brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae", size = 1493505, upload-time = "2025-11-05T18:38:20.913Z" }, - { url = "https://files.pythonhosted.org/packages/36/e5/12904bbd36afeef53d45a84881a4810ae8810ad7e328a971ebbfd760a0b3/brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03", size = 334451, upload-time = "2025-11-05T18:38:21.94Z" }, - { url = "https://files.pythonhosted.org/packages/02/8b/ecb5761b989629a4758c394b9301607a5880de61ee2ee5fe104b87149ebc/brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24", size = 369035, upload-time = "2025-11-05T18:38:22.941Z" }, { url = "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84", size = 861543, upload-time = "2025-11-05T18:38:24.183Z" }, { url = "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b", size = 444288, upload-time = "2025-11-05T18:38:25.139Z" }, { url = "https://files.pythonhosted.org/packages/3d/6f/feba03130d5fceadfa3a1bb102cb14650798c848b1df2a808356f939bb16/brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d", size = 1528071, upload-time = "2025-11-05T18:38:26.081Z" }, @@ -856,19 +821,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, @@ -965,12 +917,6 @@ version = "7.4.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/19/b6/9df434a8eeba2e6628c465a1dfa31034228ef79b26f76f46278f4ef7e49d/chardet-7.4.3.tar.gz", hash = "sha256:cc1d4eb92a4ec1c2df3b490836ffa46922e599d34ce0bb75cf41fd2bf6303d56", size = 784800, upload-time = "2026-04-13T21:33:39.803Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/52/505c207f334d51e937cbaa27ff95776e16e2d120e13cbe491cd7b3a70b50/chardet-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:25a862cddc6a9ac07023e808aedd297115345fbaabc2690479481ddc0f980e09", size = 870747, upload-time = "2026-04-13T21:32:56.916Z" }, - { url = "https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97", size = 853210, upload-time = "2026-04-13T21:32:58.309Z" }, - { url = "https://files.pythonhosted.org/packages/b9/99/f6a822ad1bde25a4c38dc3e770485e78e0893dfd871cd6e18ed3ea3a795e/chardet-7.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc50f28bad067393cce0af9091052c3b8df7a23115afd8ba7b2e0947f0cef1f8", size = 873625, upload-time = "2026-04-13T21:32:59.606Z" }, - { url = "https://files.pythonhosted.org/packages/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9", size = 883436, upload-time = "2026-04-13T21:33:01.351Z" }, - { url = "https://files.pythonhosted.org/packages/6c/63/0f43e3acf2c436fdb32a0f904aeb03a2904d2126eed34a042a194d235926/chardet-7.4.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c45e116dd51b66226a53ade3f9f635e870de5399b90e00ce45dcc311093bf4", size = 876589, upload-time = "2026-04-13T21:33:02.636Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578", size = 941866, upload-time = "2026-04-13T21:33:04.282Z" }, { url = "https://files.pythonhosted.org/packages/61/33/29de185079e6675c3f375546e30a559b7ddc75ce972f18d6e566cd9ea4eb/chardet-7.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:75d3c65cc16bddf40b8da1fd25ba84fca5f8070f2b14e86083653c1c85aee971", size = 874870, upload-time = "2026-04-13T21:33:05.977Z" }, { url = "https://files.pythonhosted.org/packages/9c/2f/4c5af01fd1a7506a1d5375403d68925eac70289229492db5aa68b58103d8/chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:29af5999f654e8729d251f1724a62b538b1262d9292cccaefddf8a02aae1ef6a", size = 854859, upload-time = "2026-04-13T21:33:07.381Z" }, { url = "https://files.pythonhosted.org/packages/36/21/edb36ad5dfa48d7f8eed97ab43931ecdaa8c15166c21b1d614967e49d681/chardet-7.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:626f00299ad62dfe937058a09572beed442ccc7b58f87aa667949b20fd3db235", size = 875032, upload-time = "2026-04-13T21:33:08.741Z" }, @@ -1002,22 +948,6 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, @@ -1151,21 +1081,6 @@ version = "7.14.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/e4/649c8d4f7f1709b6dbfc474358aa1bba02f67bcd52e2fec291a5014006cd/coverage-7.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480", size = 219795, upload-time = "2026-05-10T17:59:48.198Z" }, - { url = "https://files.pythonhosted.org/packages/7f/8d/46692d24b3f395d4cbf17bfcc57136b4f2f9c0c0df864b0bddfc1d71a014/coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4", size = 220299, upload-time = "2026-05-10T17:59:49.683Z" }, - { url = "https://files.pythonhosted.org/packages/12/c2/a40f5cb295bbcbb697a76947a56081c494c61950366294ee426ffe261099/coverage-7.14.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7", size = 250721, upload-time = "2026-05-10T17:59:51.494Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/202235eb5c3c14c212462cd91d61b7386bf8fc44bc7a77f4742d2a69174b/coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed", size = 252633, upload-time = "2026-05-10T17:59:53.244Z" }, - { url = "https://files.pythonhosted.org/packages/bb/80/5f596e8995785124ee191c42535664c5e62c65995b66f4ca21e28ae04c81/coverage-7.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980", size = 254743, upload-time = "2026-05-10T17:59:55.021Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6d/0d178825be2350f0adb27984d0aa7cf84bbdab201f6fb926b535d23a8f5f/coverage-7.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0", size = 256700, upload-time = "2026-05-10T17:59:56.511Z" }, - { url = "https://files.pythonhosted.org/packages/19/5b/9e549c2f6e9dfea472adadba06c294e64735dabc2dd19015fac082095013/coverage-7.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742", size = 250854, upload-time = "2026-05-10T17:59:57.94Z" }, - { url = "https://files.pythonhosted.org/packages/3d/1c/b94f9f5f36396021ee2f62c5834b12e6a3d31f0bed5d6fc6d1c3caec087c/coverage-7.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5", size = 252433, upload-time = "2026-05-10T17:59:59.688Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cb/d192cd8e1345eccabc32016f2d39072ecd10cb4f4b983ed8d0ebdeaf00dc/coverage-7.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327", size = 250494, upload-time = "2026-05-10T18:00:01.953Z" }, - { url = "https://files.pythonhosted.org/packages/53/c5/aac9f460a41d835dbddef1d377f105f6ac2311d0f3c1588e9f51046d8813/coverage-7.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d", size = 254261, upload-time = "2026-05-10T18:00:03.779Z" }, - { url = "https://files.pythonhosted.org/packages/23/aa/7af7c0081980a9cb3d289c5a435a4b7657dcecbd128e25c580e6a50389b5/coverage-7.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20", size = 250216, upload-time = "2026-05-10T18:00:05.262Z" }, - { url = "https://files.pythonhosted.org/packages/35/60/a4257538ce2f6b978aeb51870d6c4208c510928a03db7e0339bb625dccb7/coverage-7.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c", size = 251125, upload-time = "2026-05-10T18:00:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ab/f91af47642ec1aa53490e835a95847168d9c77fc39aa58527604c051e145/coverage-7.14.0-cp311-cp311-win32.whl", hash = "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3", size = 222300, upload-time = "2026-05-10T18:00:08.608Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f0/a71ddbd874431e7a7cd96071f0c331cfbbad07704833c765d24ffbab8a67/coverage-7.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1", size = 223241, upload-time = "2026-05-10T18:00:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/d8/6e/d9d312a5151a96cd110efee32efc3fc97b01ebd86203fe618ccb29cf4c92/coverage-7.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627", size = 221908, upload-time = "2026-05-10T18:00:12.242Z" }, { url = "https://files.pythonhosted.org/packages/09/1e/2f996b2c8415cbb6f54b0f5ec1ee850c96d7911961afb4fc05f4a89d8c58/coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", size = 219967, upload-time = "2026-05-10T18:00:13.756Z" }, { url = "https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", size = 220329, upload-time = "2026-05-10T18:00:15.264Z" }, { url = "https://files.pythonhosted.org/packages/75/cf/a8f4b43a16e194b0261257ad28ded5853ec052570afef4a84e1d81189f3b/coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", size = 251839, upload-time = "2026-05-10T18:00:17.16Z" }, @@ -1244,11 +1159,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, ] -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11' and platform_python_implementation != 'PyPy'" }, -] - [[package]] name = "crispy-bootstrap3" version = "2024.1" @@ -1347,12 +1257,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, - { url = "https://files.pythonhosted.org/packages/9d/3e/e54cde8c01631a5a8226ccd617eab9e57fd5cfdad90f1a9e6bb570794631/cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c", size = 3963170, upload-time = "2026-07-31T14:24:51.968Z" }, - { url = "https://files.pythonhosted.org/packages/01/b6/0b9e125e90f3d2dcf599a218a899cda7326a3158cfa258723f0b398b08f6/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a", size = 4692441, upload-time = "2026-07-31T14:24:53.743Z" }, - { url = "https://files.pythonhosted.org/packages/53/c9/a5151588710785a96d7bc4de27d4cd62f263bbbcb203cfe29df537eb6505/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e", size = 4699810, upload-time = "2026-07-31T14:24:55.746Z" }, - { url = "https://files.pythonhosted.org/packages/c7/1a/15b92b25eb6ce3089cd49377ae990a0f3ad485a510f968aed1f19dbdcdf2/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d", size = 4691924, upload-time = "2026-07-31T14:24:58.082Z" }, - { url = "https://files.pythonhosted.org/packages/62/15/219075012ab13e8905f3cd572204f4acb4b111df787104346b9bc0cea789/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437", size = 4699593, upload-time = "2026-07-31T14:24:59.951Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" }, ] [[package]] @@ -1449,16 +1353,16 @@ wheels = [ [[package]] name = "django" -version = "5.2.16" +version = "6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref", marker = "platform_python_implementation != 'PyPy'" }, { name = "sqlparse", marker = "platform_python_implementation != 'PyPy'" }, { name = "tzdata", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/26/889449d521ae508b26de715954faecd8bcf3f740affb81b2d146a83b42a5/django-5.2.16.tar.gz", hash = "sha256:59ea02020c3136fce14bef0bbece21a10a4febef5eed1c51c22ae468efa22200", size = 10890894, upload-time = "2026-07-07T13:52:17.005Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/42/6cb20996733984c1f6661daeda3877990836c76c633c6c8879d39f7120eb/django-6.1.tar.gz", hash = "sha256:86a2aacd59b817e4d6ac2ebfe22356c58f66f7b24e503f71b7c2fead677ee48b", size = 11223034, upload-time = "2026-08-05T19:21:53.789Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/13/1e5e3e4c15dcecb04281b3cb2a46a4670e1cef131068e202f6040df19224/django-5.2.16-py3-none-any.whl", hash = "sha256:04f354bf9d807a86ad1a8392fe3808d362358a8eafc322848e0e43e59b24371d", size = 8311943, upload-time = "2026-07-07T13:52:11.223Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/ce847620134cfab903e75690c498af73b46abbede2912ea89bd76d5c1e76/django-6.1-py3-none-any.whl", hash = "sha256:6c132cd980c9392b06807d4ca52d72530d631dc65a85d9dacede00a780cefbbe", size = 8417399, upload-time = "2026-08-05T19:21:47.285Z" }, ] [[package]] @@ -1923,7 +1827,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django", marker = "platform_python_implementation != 'PyPy'" }, { name = "django-stubs-ext", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/eb/594712952364f0b1f009b9c414940a09031d07fe95bb888e92f50aa9a550/django_fsm_2-4.2.4.tar.gz", hash = "sha256:0e395afd03ff504afd476e8568ca213aafc3324bd4e5427e5b3287f7b1e7d979", size = 21544, upload-time = "2026-03-16T20:33:33.476Z" } wheels = [ @@ -2105,7 +2008,7 @@ wheels = [ [[package]] name = "django-pbn-client" version = "0.2.2" -source = { git = "https://github.com/iplweb/django-pbn-client.git?rev=bb952e98967a96f96cd3773e3dcfcaf4555c8690#bb952e98967a96f96cd3773e3dcfcaf4555c8690" } +source = { git = "https://github.com/iplweb/django-pbn-client.git?rev=1b91ac4f968a971815160863cc7297a17dc8712b#1b91ac4f968a971815160863cc7297a17dc8712b" } dependencies = [ { name = "django", marker = "platform_python_implementation != 'PyPy'" }, { name = "pbn-client", marker = "platform_python_implementation != 'PyPy'" }, @@ -2150,16 +2053,17 @@ wheels = [ ] [[package]] -name = "django-redis-iplweb" -version = "6.0.1" +name = "django-redis" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django", marker = "platform_python_implementation != 'PyPy'" }, { name = "redis", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/55/890106c7ff23f444cd3857d2e3c05f7ea329c9608f803e3d66cf615e421f/django_redis_iplweb-6.0.1.tar.gz", hash = "sha256:faa67d5d6d83d27376912dfd3a34feda696d08e767deeb802b51d69d490ce8d0", size = 60877, upload-time = "2026-04-26T19:29:06.654Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/78/203a0cdc0f1c083a5407d01f77b58099a120bb8a5a04562f56a9bb341314/django_redis-7.0.0.tar.gz", hash = "sha256:e48491c862f4350b0747ceb1016700686fb93c4f4e0fb9c490fe6c6658ffd933", size = 64601, upload-time = "2026-06-02T14:17:48.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/89/538de728143bfb0e6a261758e4db1a5821e1512485d5690b5de673011664/django_redis_iplweb-6.0.1-py3-none-any.whl", hash = "sha256:98402a64c5f3160bcd07c9957f511db094ac3cc80f7fe1248d413c57b1f4be47", size = 36474, upload-time = "2026-04-26T19:29:04.73Z" }, + { url = "https://files.pythonhosted.org/packages/7e/9f/09cdb9a1eebe8533b02a7694ca787acfc1e4d93b5b6175ff99366d4e6d64/django_redis-7.0.0-py3-none-any.whl", hash = "sha256:4b23aa6e0cd0937bb1242e9a463809e6004de3ca2150f34e986306bb6220d688", size = 38932, upload-time = "2026-06-02T14:17:47.281Z" }, ] [[package]] @@ -2392,15 +2296,11 @@ wheels = [ [[package]] name = "djangorestframework" -version = "3.17.1" -source = { registry = "https://pypi.org/simple" } +version = "3.17.2" +source = { git = "https://github.com/encode/django-rest-framework.git?rev=80d535ddf08226020a9f44ca57f6eb1bbd91fa6a#80d535ddf08226020a9f44ca57f6eb1bbd91fa6a" } dependencies = [ { name = "django", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/d7/c016e69fac19ff8afdc89db9d31d9ae43ae031e4d1993b20aca179b8301a/djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", size = 905742, upload-time = "2026-03-24T16:58:33.705Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/e1/2c516bdc83652b1a60c6119366ac2c0607b479ed05cd6093f916ca8928f8/djangorestframework-3.17.1-py3-none-any.whl", hash = "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457", size = 898844, upload-time = "2026-03-24T16:58:31.845Z" }, -] [[package]] name = "djlint" @@ -2417,15 +2317,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/62/33/7efcde4581d1295c1e64a75b6d1ac444b4c2557b668e73032070ff5fc081/djlint-1.43.2.tar.gz", hash = "sha256:5a783d9b419b0833be1a13f213c0ea5e4a86aa505bd5d159884a2a349d06f7e8", size = 79429, upload-time = "2026-08-01T18:11:35.424Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/cc/85cab9b9886261498859b263c6dceb296befe63abc9dc4a7f8ad0a56deb6/djlint-1.43.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df270554a9f83ddc1e454b2e7e4fef612f55ddb11764c62fd6a4a2104af43670", size = 779610, upload-time = "2026-08-01T18:10:38.025Z" }, - { url = "https://files.pythonhosted.org/packages/be/c6/0d92375ded2ac44be70d11b5f9b90f0821be6b59321eb9818cc5f4e76ef8/djlint-1.43.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a8a8f9635579fde6b68a2ee3e1bfdb82cf8732cbd07acb3bc28e8f5cf6664ab5", size = 736502, upload-time = "2026-08-01T18:10:39.094Z" }, - { url = "https://files.pythonhosted.org/packages/7d/3b/a129737f0dc5887725e8fb6ed46592404f86895812d8eb1c069ea9e93614/djlint-1.43.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd5f610f51b7575c13b942aca4f33c8fb9395b14504b27b489734ae79e2437f1", size = 782593, upload-time = "2026-08-01T18:10:40.305Z" }, - { url = "https://files.pythonhosted.org/packages/e2/af/1d8162323f2447dd90112c3dd25ea667abf9dfcdc4e29cad0c42c9298efd/djlint-1.43.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f7209dd6137712ff4c97efe2049a7787710de727643d6278dd51985523c568", size = 813209, upload-time = "2026-08-01T18:10:41.325Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ec/0b64ee733984d5774c7506594bc119083f7cca3e791bb0000ecfdf5ab466/djlint-1.43.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09e88f73d275f0fce0cc182b6768944fb5992dd1c33eb8f4e5f6f7f04fc02aa5", size = 794987, upload-time = "2026-08-01T18:10:42.771Z" }, - { url = "https://files.pythonhosted.org/packages/ef/c9/3d4c0865bc7ca22dd69b431f310e7ae332a80228dc6cb269996955e498da/djlint-1.43.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5e63439c75cf4053a6a3fa78cc8fd233581eae997297b9c6f1862b6fc45c69e6", size = 827108, upload-time = "2026-08-01T18:10:44.14Z" }, - { url = "https://files.pythonhosted.org/packages/8e/63/3ccbf787dda549756983b1c13786fe5c2d49c2990b17730101448954cc55/djlint-1.43.2-cp311-cp311-win32.whl", hash = "sha256:1da8d2a43c3d8b9dce26b6b25b6cf5810ff867a07c6d4c09732b79e46ed87505", size = 598272, upload-time = "2026-08-01T18:10:45.24Z" }, - { url = "https://files.pythonhosted.org/packages/10/0b/2965d6e4fa892cb889bc43979a482a70c5a0eb145133457a71ee9a359b5f/djlint-1.43.2-cp311-cp311-win_amd64.whl", hash = "sha256:396dcc23b75dd8d4d113f68c4b3f40d6310385d8c07a953b40535d86a5e44874", size = 679596, upload-time = "2026-08-01T18:10:46.606Z" }, - { url = "https://files.pythonhosted.org/packages/2f/7a/0d9da8d2186509c9279801348a3704cd6c0db61693782fbf5d8068c55ced/djlint-1.43.2-cp311-cp311-win_arm64.whl", hash = "sha256:15b713a79a59feceb9dc6ee9c5cc8813f57f262e11566e2a8f0d2806da824dde", size = 600717, upload-time = "2026-08-01T18:10:47.631Z" }, { url = "https://files.pythonhosted.org/packages/39/d3/2d91ba6789a31d36c5a60b50b7ee0e438e49f0d5292906f1490548877f89/djlint-1.43.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b408ab06be47b5698246bacd5c52a5bb521004bf020101de3c97f05272c7187c", size = 798549, upload-time = "2026-08-01T18:10:48.972Z" }, { url = "https://files.pythonhosted.org/packages/48/8a/f0a709a49160373f8c1830c74f3430cecf4e73256b5d45c9cb651439ac7e/djlint-1.43.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f72b2fa7576c2d1dc354526200d24ab31497e58e873b7fd7f65e7b434b51c084", size = 746050, upload-time = "2026-08-01T18:10:50.269Z" }, { url = "https://files.pythonhosted.org/packages/89/f7/cff8daea9afc14c6fbe69eaf4bb45241b6fd8a1a55332a1700e5521d9447/djlint-1.43.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0f786367a863981d474ddef25f4567f32ffe4615a36da2e8cdf5023e0454f0d", size = 785811, upload-time = "2026-08-01T18:10:51.449Z" }, @@ -2554,14 +2445,6 @@ version = "4.62.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, - { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, - { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, - { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, @@ -2692,11 +2575,6 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, - { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, @@ -2712,8 +2590,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, - { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] [[package]] @@ -2746,17 +2622,6 @@ version = "3.2.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, - { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, - { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, - { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, - { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, - { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, - { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, @@ -2852,13 +2717,6 @@ version = "0.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, - { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, - { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, - { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, @@ -2949,18 +2807,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/7b/04ab6afa1ff7eb9ccb09049918c0407b205f5009092c0416147d163e4e2b/immutabledict-4.2.2-py3-none-any.whl", hash = "sha256:97c31d098a2c850e93a958badeef765e4736ed7942ec73e439facd764a3a7217", size = 4736, upload-time = "2025-10-12T13:32:58.326Z" }, ] -[[package]] -name = "importlib-metadata" -version = "9.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, -] - [[package]] name = "incremental" version = "24.7.2" @@ -3018,7 +2864,6 @@ dependencies = [ { name = "pygments", marker = "platform_python_implementation != 'PyPy'" }, { name = "stack-data", marker = "platform_python_implementation != 'PyPy'" }, { name = "traitlets", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } wheels = [ @@ -3059,9 +2904,6 @@ wheels = [ name = "jaraco-context" version = "6.1.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backports-tarfile", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, @@ -3161,7 +3003,6 @@ name = "keyring" version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, { name = "jaraco-classes", marker = "platform_python_implementation != 'PyPy'" }, { name = "jaraco-context", marker = "platform_python_implementation != 'PyPy'" }, { name = "jaraco-functools", marker = "platform_python_implementation != 'PyPy'" }, @@ -3222,22 +3063,6 @@ version = "6.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9", size = 8526232, upload-time = "2026-04-18T04:27:40.389Z" }, - { url = "https://files.pythonhosted.org/packages/a7/51/adc8826570a112f83bb4ddb3a2ab510bbc2ccd62c1b9fe1f34fae2d90b57/lxml-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50", size = 4595448, upload-time = "2026-04-18T04:27:44.208Z" }, - { url = "https://files.pythonhosted.org/packages/54/84/5a9ec07cbe1d2334a6465f863b949a520d2699a755738986dcd3b6b89e3f/lxml-6.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5", size = 4923771, upload-time = "2026-04-18T04:32:17.402Z" }, - { url = "https://files.pythonhosted.org/packages/a7/23/851cfa33b6b38adb628e45ad51fb27105fa34b2b3ba9d1d4aa7a9428dfe0/lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e", size = 5068101, upload-time = "2026-04-18T04:32:21.437Z" }, - { url = "https://files.pythonhosted.org/packages/b0/38/41bf99c2023c6b79916ba057d83e9db21d642f473cac210201222882d38b/lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512", size = 5002573, upload-time = "2026-04-18T04:32:25.373Z" }, - { url = "https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c", size = 5202816, upload-time = "2026-04-18T04:32:29.393Z" }, - { url = "https://files.pythonhosted.org/packages/9a/da/bc710fad8bf04b93baee752c192eaa2210cd3a84f969d0be7830fea55802/lxml-6.1.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5", size = 5329999, upload-time = "2026-04-18T04:32:34.019Z" }, - { url = "https://files.pythonhosted.org/packages/b3/cb/bf035dedbdf7fab49411aa52e4236f3445e98d38647d85419e6c0d2806b9/lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289", size = 4659643, upload-time = "2026-04-18T04:32:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/22be31f33727a5e4c7b01b0a874503026e50329b259d3587e0b923cf964b/lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a", size = 5265963, upload-time = "2026-04-18T04:32:41.881Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2b/d44d0e5c79226017f4ab8c87a802ebe4f89f97e6585a8e4166dffcdd7b6e/lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3", size = 5045444, upload-time = "2026-04-18T04:32:44.512Z" }, - { url = "https://files.pythonhosted.org/packages/d3/c3/3f034fec1594c331a6dbf9491238fdcc9d66f68cc529e109ec75b97197e1/lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9", size = 4712703, upload-time = "2026-04-18T04:32:47.16Z" }, - { url = "https://files.pythonhosted.org/packages/12/16/0b83fccc158218aca75a7aa33e97441df737950734246b9fffa39301603d/lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11", size = 5252745, upload-time = "2026-04-18T04:32:50.427Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ee/12e6c1b39a77666c02eaa77f94a870aaf63c4ac3a497b2d52319448b01c6/lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4", size = 5226822, upload-time = "2026-04-18T04:32:53.437Z" }, - { url = "https://files.pythonhosted.org/packages/34/20/c7852904858b4723af01d2fc14b5d38ff57cb92f01934a127ebd9a9e51aa/lxml-6.1.0-cp311-cp311-win32.whl", hash = "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3", size = 3594026, upload-time = "2026-04-18T04:27:31.903Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7", size = 4025114, upload-time = "2026-04-18T04:27:34.077Z" }, - { url = "https://files.pythonhosted.org/packages/c2/df/c84dcc175fd690823436d15b41cb920cd5ba5e14cd8bfb00949d5903b320/lxml-6.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39", size = 3667742, upload-time = "2026-04-18T04:27:38.45Z" }, { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, @@ -3310,12 +3135,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, - { url = "https://files.pythonhosted.org/packages/f2/88/55143966481409b1740a3ac669e611055f49efd68087a5ce41582325db3e/lxml-6.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842", size = 3930134, upload-time = "2026-04-18T04:32:35.008Z" }, - { url = "https://files.pythonhosted.org/packages/b5/97/28b985c2983938d3cb696dd5501423afb90a8c3e869ef5d3c62569282c0f/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c", size = 4210749, upload-time = "2026-04-18T04:36:03.626Z" }, - { url = "https://files.pythonhosted.org/packages/29/67/dfab2b7d58214921935ccea7ce9b3df9b7d46f305d12f0f532ac7cf6b804/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de", size = 4318463, upload-time = "2026-04-18T04:36:06.309Z" }, - { url = "https://files.pythonhosted.org/packages/32/a2/4ac7eb32a4d997dd352c32c32399aae27b3f268d440e6f9cfa405b575d2f/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635", size = 4251124, upload-time = "2026-04-18T04:36:09.056Z" }, - { url = "https://files.pythonhosted.org/packages/33/ef/d6abd850bb4822f9b720cfe36b547a558e694881010ff7d012191e8769c6/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037", size = 4401758, upload-time = "2026-04-18T04:36:11.803Z" }, - { url = "https://files.pythonhosted.org/packages/40/44/3ee09a5b60cb44c4f2fbc1c9015cfd6ff5afc08f991cab295d3024dcbf2d/lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace", size = 3508860, upload-time = "2026-04-18T04:32:48.619Z" }, ] [[package]] @@ -3324,15 +3143,6 @@ version = "1.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/77/5d/e235921b5b74818cb65b557fa05cc6201c2c1612d4866ff75c835bcf808d/marisa_trie-1.4.1.tar.gz", hash = "sha256:44ce3bdbeb7c950d463e460184fc3e18702df9ef0edb826bac672fd789fb1d20", size = 261581, upload-time = "2026-04-08T07:17:52.991Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/94/1ad851729ba0cdbc269bdd72b4b725cfbf5a25e4186fd12e56836d2d52ba/marisa_trie-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:579d1498e6b9e8f139b36601d2ea35e9239e849ff1615f3c3fc8df8ce4d3a936", size = 208644, upload-time = "2026-04-08T07:16:25.926Z" }, - { url = "https://files.pythonhosted.org/packages/11/67/2a8870ef1c42412ace3d656830902893fad6239434cde749f6654f907b41/marisa_trie-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a6404610eca835cf179c4407bcaa00d7acfbf3fd7aafcc1413d3adc262b554c", size = 192740, upload-time = "2026-04-08T07:16:26.971Z" }, - { url = "https://files.pythonhosted.org/packages/b7/3f/d1d67e1ae5058dfcbb3756f75a26ea203acae875584636d360fd4a38b248/marisa_trie-1.4.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:284ff4b2a63f00e175c7fe88d18c23a556c988ce705eb8e15a65e60ad7f86a98", size = 1506335, upload-time = "2026-04-08T07:16:28.378Z" }, - { url = "https://files.pythonhosted.org/packages/da/23/086b81133baccbc05feb2fbc4113c70775352f3b9851dc8a20d69b2db44f/marisa_trie-1.4.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29eb718078431518d13037830c50023333721b146ca58eb78889aabfa60f4c33", size = 1529146, upload-time = "2026-04-08T07:16:30.061Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a0/c717ffb7697f059cde03b75c9facf6fd4ee18ce720513156bb03e875d215/marisa_trie-1.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ac478766ff9381f1bc18f39f694388f64c20cfa8cb2b308e41ece2b4ce05467", size = 2436516, upload-time = "2026-04-08T07:16:31.452Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d7/c5f5cd9eb32a34447971ee3430410b19087564137d62da7bd348b40e684a/marisa_trie-1.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7024c2cb001442fe04b9720be105bbe01ff2d7f357b70fa44d42272abf7da1f", size = 2532453, upload-time = "2026-04-08T07:16:32.711Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/84fe4788b29a81e00efb4c97f3c1b7cae0dbda14369dc7ee2659823bfd83/marisa_trie-1.4.1-cp311-cp311-win32.whl", hash = "sha256:c059562d5aea86bf623a2c440b8595a86c0de553ca96986e4d36f25d07570d5b", size = 140594, upload-time = "2026-04-08T07:16:33.883Z" }, - { url = "https://files.pythonhosted.org/packages/d5/b0/3f793c5f7727ca1d36f9d53b9fc48804942918a9f460b6d46988770677b2/marisa_trie-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:c74606bd7e0066f20cf7187de44c955ba3b4ce85159a43f8cd8b0ee982ea4c4c", size = 175072, upload-time = "2026-04-08T07:16:35.254Z" }, - { url = "https://files.pythonhosted.org/packages/fe/8c/ac1851b7c9ce881343bf0639c902aaa2b767a31dc79d6af5950413a6ba66/marisa_trie-1.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:59a5c286329a5defa33c40cce1f16c9829e4128b57ecc851ac32a7d1071913d5", size = 142560, upload-time = "2026-04-08T07:16:36.425Z" }, { url = "https://files.pythonhosted.org/packages/b3/b7/89811f7eba6e92386279376df81cfa281ab99e30f7e4f5a5e04d8dba6b99/marisa_trie-1.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:63964dedbf49ef0d17cb32d368f13ec71ca0ec026976b1cc24cb6a993d05752a", size = 206731, upload-time = "2026-04-08T07:16:37.439Z" }, { url = "https://files.pythonhosted.org/packages/f7/8b/cc34313149486dfc13e84303e12d61fd55788b37d92c3e082cc3d142e776/marisa_trie-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87e65dff37d1b9edea7bc7a8e935c851ec4934f2e56071a4501ce8db97b579a4", size = 190988, upload-time = "2026-04-08T07:16:38.686Z" }, { url = "https://files.pythonhosted.org/packages/15/0c/376e21c62bd0e658a5e9f6b8912f3116591778c639857ad374c7639ceebe/marisa_trie-1.4.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7bed50d39ff1391a67b9383a7f3c458a1a0cb40fe8dd16952f813fbf8939eeff", size = 1471836, upload-time = "2026-04-08T07:16:39.88Z" }, @@ -3407,17 +3217,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, @@ -3502,13 +3301,6 @@ version = "0.18.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/77/b7/83dc18bef0cd6f4268d1a63dd682730d3c1150d77a973a34c8de63610bdc/minify_html-0.18.1.tar.gz", hash = "sha256:43998530ef537701f003a8e908b756d78eff303c86b041a95855e290518ba79c", size = 96577, upload-time = "2025-10-25T22:27:18.801Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/f1/03fa8d0f8801c9a13a3330715890a88424c5e4277b895713f8aeea9c3543/minify_html-0.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:aa9ce0978b03b4040ef72f4eb6a367bd615165d88b5c2363c098efa3d60d7855", size = 3062942, upload-time = "2025-10-25T22:39:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/21/d0/3403a9a782b8012196e138f963be0d0f8d9e244af6069486860afbac9944/minify_html-0.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:91791ea8a6c5f6cc227dc9febd036382e3ac7f93c157d48599f9668a5e813339", size = 2828941, upload-time = "2025-10-25T22:58:22.088Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e6/bc21600265679476da5f90d74212aa84ee413eb1e4068238d5f34d8cb531/minify_html-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a20c648f26b600a55ea2f3f8e8c1c2797408890cfe453e58a151c3bcd1a088fb", size = 2900823, upload-time = "2025-10-25T22:27:51.596Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0b/81e7135dd922bd9a0d3c835d829e9a145193da6ac5cc21b1e8bc9c8c9f9f/minify_html-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b92f40bab8178cbc39a0e2c602513b6478b9489e4b99c5452a680342881db7d8", size = 3083123, upload-time = "2025-10-25T22:27:24.31Z" }, - { url = "https://files.pythonhosted.org/packages/23/97/7468805064065af619db0a5bba8490ccdad976607e7b1abf671be85c3b3e/minify_html-0.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af83d722fe73e1e571da1130d09f06358cf507a18c153c72a4e56c276e7305af", size = 3082360, upload-time = "2025-10-25T22:31:09.786Z" }, - { url = "https://files.pythonhosted.org/packages/4c/53/05b2d57a2cd1f5da5e8848f180074d4b2963025c57082d87b9c472d82177/minify_html-0.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5c3e4a711cd51643cb0b76d24fdd74646e55f0a92ae3c3ef2f8a6746f6b7ae4", size = 3328307, upload-time = "2025-10-25T22:27:19.346Z" }, - { url = "https://files.pythonhosted.org/packages/60/c7/fb44633a499fda905b176405cc82917a4ce7261a21f135a5cf20465d341c/minify_html-0.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:d99db3db6208729aea917a884413eed0850148792bc33fc81f70ec9e41465906", size = 3118683, upload-time = "2025-10-25T22:36:42.195Z" }, { url = "https://files.pythonhosted.org/packages/4e/2b/b6f715517caeae632977d89e4d213df3a125c976112e17704ee3e18f511e/minify_html-0.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fe625fae576d20f0fe5981f0f7a5fe6d96608bbb8daf4815f7a0b28be7d62472", size = 3061927, upload-time = "2025-10-25T22:41:31.329Z" }, { url = "https://files.pythonhosted.org/packages/30/37/ffef9cb491b0830a56ec29d4a13bc09a7a92e0ca9d48dd5690ba7304a91f/minify_html-0.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3e9a91dc200c0a99e0b3c577b44aee0aa449aaf510464197f198e94b7bdf2d48", size = 2828402, upload-time = "2025-10-25T22:52:37.004Z" }, { url = "https://files.pythonhosted.org/packages/17/cc/07587b26e266cacfa2d640d479a0a7978632a4b38cac55ef93aac0b3ad91/minify_html-0.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:854590f1fc1b2ba8f8cd26e925030a37fb6e042545d0cef2b44d0d1942d02943", size = 2900527, upload-time = "2025-10-25T22:27:48.511Z" }, @@ -3593,17 +3385,6 @@ version = "1.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" }, - { url = "https://files.pythonhosted.org/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" }, - { url = "https://files.pythonhosted.org/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" }, - { url = "https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" }, - { url = "https://files.pythonhosted.org/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" }, - { url = "https://files.pythonhosted.org/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" }, - { url = "https://files.pythonhosted.org/packages/57/cf/e673683c4c6c90c1022b24c65af4b03eda72b182a1176ef6449069d66acc/msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d", size = 64091, upload-time = "2026-06-18T16:12:54.821Z" }, - { url = "https://files.pythonhosted.org/packages/3f/07/ca212739d179f9083bff2c7c08c24101c3555a334fadc2b876b18768a3ae/msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8", size = 70462, upload-time = "2026-06-18T16:12:55.898Z" }, - { url = "https://files.pythonhosted.org/packages/6d/be/6798347b425e26f35db82e69dd83c09716c856a3714e7bffc4c0860fd830/msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66", size = 65059, upload-time = "2026-06-18T16:12:57.053Z" }, { url = "https://files.pythonhosted.org/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" }, { url = "https://files.pythonhosted.org/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" }, { url = "https://files.pythonhosted.org/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" }, @@ -3708,17 +3489,6 @@ version = "2.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, - { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, - { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, - { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, - { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, - { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, - { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, - { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, - { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, @@ -3772,13 +3542,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, - { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, - { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, - { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, - { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] [[package]] @@ -3815,11 +3578,6 @@ dependencies = [ { name = "typing-extensions", marker = "platform_python_implementation != 'PyPy'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/16/a08369f1d2022b5aea8d9ceede08df417622015447b817019e11ef4a1d9b/ortools-9.15.6755-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:55e291560d2fdb9590656cbee06ba99ee7f2476bd7d316ff757eeab33e9b20d6", size = 23886068, upload-time = "2026-01-14T15:38:55.095Z" }, - { url = "https://files.pythonhosted.org/packages/62/0f/302f019d08ec5870fae6d0d2075d2fbcef2f39ef8104d2ae6563b2c7a0b5/ortools-9.15.6755-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e51ae55569650e5381fd6e50c655ccf6368a9532f5720ea41396bb90e0247a21", size = 21910495, upload-time = "2026-01-14T15:38:58.429Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0a/aca166f878189acadaadb43d6f97aab1288a082947e7aeb56d866915daed/ortools-9.15.6755-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3bcccd15ef3fc6ac10bfa11630ba6dfe437d4fd1374a5b33f4773b7fee0f877", size = 27649965, upload-time = "2026-01-14T15:37:50.311Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7c/8c732ddea429ad009553ee278f9a4aacb1e8b950a76647c36514d5619a1d/ortools-9.15.6755-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a85a68ffb3fc1967e78624f40d3707aae459e82e3f2d9fe02e91788b3c7bf2a", size = 29821193, upload-time = "2026-01-14T15:37:53.961Z" }, - { url = "https://files.pythonhosted.org/packages/a3/30/2574d469613b8fe14664ffc7b01887a0afa385f6052bb8a1194e3df15e4a/ortools-9.15.6755-cp311-cp311-win_amd64.whl", hash = "sha256:781fb09d6c9f46015291f706bd7c7e0815db1bec6e92c74716342fb7ea2d0532", size = 23861559, upload-time = "2026-01-14T15:39:42.166Z" }, { url = "https://files.pythonhosted.org/packages/6a/fc/9fa53f1a13710e6183df4d00fe4988c79a55b501e282645d49f1e250437f/ortools-9.15.6755-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:ae1c6e1fd844b4d756b22eb6c0ed574ea4342ee206d807c4f903039e748228fa", size = 23926322, upload-time = "2026-01-14T15:39:01.626Z" }, { url = "https://files.pythonhosted.org/packages/f1/b6/7e6618ef7a88e8eb706a8a876806b4d336f1bef8c574f8a02d2da3e483ef/ortools-9.15.6755-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e16686c2b457fa6242c474ab890ee1712347ab53678e0d2fab307ae03e97a4b", size = 21912940, upload-time = "2026-01-14T15:39:04.403Z" }, { url = "https://files.pythonhosted.org/packages/86/a9/37cb31fc5ffbec2650ebb0d2538a83842b5693a788a0ec6057559dab1169/ortools-9.15.6755-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3cd6bec0a2e00e3891a53e3b436f45a1000269f302085572f49e9856b7f8eaf0", size = 27646561, upload-time = "2026-01-14T15:37:57.414Z" }, @@ -3873,14 +3631,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, - { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, - { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, - { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, - { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, - { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717, upload-time = "2026-07-22T22:18:08.307Z" }, { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095, upload-time = "2026-07-22T22:18:10.6Z" }, { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458, upload-time = "2026-07-22T22:18:12.834Z" }, @@ -4015,15 +3765,6 @@ version = "12.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, - { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, - { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, - { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, - { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, - { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, - { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, @@ -4066,11 +3807,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, - { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, - { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, - { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, - { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, ] [[package]] @@ -4239,17 +3975,6 @@ version = "2.9.12" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686, upload-time = "2026-04-21T09:40:34.304Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/19/d4ce60954f3bb9d8e3bc5e5c4d1f2487de2d3851bf2391d54954c9df12a6/psycopg2_binary-2.9.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5c8ce6c61bd1b1f6b9c24ee32211599f6166af2c55abb19456090a21fd16554b", size = 3712338, upload-time = "2026-04-20T23:34:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/53/71/c85409ee0d78890f0660eff262e815e7dd2bb741a17611d82e9e8cd9dc5e/psycopg2_binary-2.9.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4a9eaa6e7f4ff91bec10aa3fb296878e75187bced5cc4bafe17dc40915e1326", size = 3822407, upload-time = "2026-04-20T23:34:05.977Z" }, - { url = "https://files.pythonhosted.org/packages/3c/ed/60486c2c7f0d4d1ede2bfb1ed27e2498477ce646bc7f6b2759906303117e/psycopg2_binary-2.9.12-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c6528cefc8e50fcc6f4a107e27a672058b36cc5736d665476aeb413ba88dbb06", size = 4578425, upload-time = "2026-04-20T23:34:08.246Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b9/656cb03fad9f4f49f2145c334b1126ee75189929ca4e6187d485a2d59951/psycopg2_binary-2.9.12-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4e184b1fb6072bf05388aa41c697e1b2d01b3473f107e7ec44f186a32cfd0b8", size = 4273709, upload-time = "2026-04-20T23:34:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/99/66/08cf0da0e25cc6fb142c89be45fc8418792858f0c4cbff5e24530ff02cd6/psycopg2_binary-2.9.12-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4766ab678563054d3f1d064a4db19cc4b5f9e3a8d9018592a8285cf200c248f3", size = 5893779, upload-time = "2026-04-20T23:34:13.905Z" }, - { url = "https://files.pythonhosted.org/packages/17/d7/eecd9ce8e146d3721115d82d3836efdbb712187e4590325df549989d18f4/psycopg2_binary-2.9.12-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5a0253224780c978746cb9be55a946bcdaf40fe3519c0f622924cdabdafe2c39", size = 4109308, upload-time = "2026-04-20T23:34:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2e/b1dc289b362cc8d45697b57eefbd673186f49a4ea0906928988e3affcc98/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0dc9228d47c46bda253d2ecd6bb93b56a9f2d7ad33b684a1fa3622bf74ffe30c", size = 3654405, upload-time = "2026-04-20T23:34:19.303Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e4/4c4aea6473214dbdbd0fbba11aa4691e76dc01722c55724c5951719865ff/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f921f3cd87035ef7df233383011d7a53ea1d346224752c1385f1edfd790ceb6a", size = 3299187, upload-time = "2026-04-20T23:34:21.206Z" }, - { url = "https://files.pythonhosted.org/packages/ba/5d/b03b99986446a4f57b170ed9a2579fb7ff9783ca0fa5226b19db99737fee/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d999bd982a723113c1a45b55a7a6a90d64d0ed2278020ed625c490ff7bef96c", size = 3047716, upload-time = "2026-04-20T23:34:23.077Z" }, - { url = "https://files.pythonhosted.org/packages/14/86/382ee4afbd1d97500c9d2862b20c2fdeddf4b7335e984df3fb4309f64108/psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29d4d134bd0ab46ffb04e94aa3c5fa3ef582e9026609165e2f758ff76fc3a3be", size = 3349237, upload-time = "2026-04-20T23:34:25.211Z" }, - { url = "https://files.pythonhosted.org/packages/a8/16/9a57c75ba1eda7165c017342f526810d5f5a12647dde749c99ae9a7141d7/psycopg2_binary-2.9.12-cp311-cp311-win_amd64.whl", hash = "sha256:cb4a1dacdd48077150dc762a9e5ddbf32c256d66cb46f80839391aa458774936", size = 2757036, upload-time = "2026-04-20T23:34:27.77Z" }, { url = "https://files.pythonhosted.org/packages/e2/9f/ef4ef3c8e15083df90ca35265cfd1a081a2f0cc07bb229c6314c6af817f4/psycopg2_binary-2.9.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5cdc05117180c5fa9c40eea8ea559ce64d73824c39d928b7da9fb5f6a9392433", size = 3712459, upload-time = "2026-04-20T23:34:30.549Z" }, { url = "https://files.pythonhosted.org/packages/b5/01/3dd14e46ba48c1e1a6ec58ee599fa1b5efa00c246d5046cd903d0eeb1af1/psycopg2_binary-2.9.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3227a3bc228c10d21011a99245edca923e4e8bf461857e869a507d9a41fe9f6", size = 3822936, upload-time = "2026-04-20T23:34:32.77Z" }, { url = "https://files.pythonhosted.org/packages/a6/f7/0640e4901119d8a9f7a1784b927f494e2198e213ceb593753d1f2c8b1b30/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:995ce929eede89db6254b50827e2b7fd61e50d11f0b116b29fffe4a2e53c4580", size = 4578676, upload-time = "2026-04-20T23:34:35.18Z" }, @@ -4330,9 +4055,6 @@ version = "1.29.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/22/d9/1728840a22a4ef8a8f479b9156aa2943cd98c3907accd3849fb0d5f82bfd/pycairo-1.29.0.tar.gz", hash = "sha256:f3f7fde97325cae80224c09f12564ef58d0d0f655da0e3b040f5807bd5bd3142", size = 665871, upload-time = "2025-11-11T19:13:01.584Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/92/1b904087e831806a449502786d47d3a468e5edb8f65755f6bd88e8038e53/pycairo-1.29.0-cp311-cp311-win32.whl", hash = "sha256:12757ebfb304b645861283c20585c9204c3430671fad925419cba04844d6dfed", size = 751342, upload-time = "2025-11-11T19:11:37.386Z" }, - { url = "https://files.pythonhosted.org/packages/db/09/a0ab6a246a7ede89e817d749a941df34f27a74bedf15551da51e86ae105e/pycairo-1.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:3391532db03f9601c1cee9ebfa15b7d1db183c6020f3e75c1348cee16825934f", size = 845036, upload-time = "2025-11-11T19:11:43.408Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/bf455454bac50baef553e7356d36b9d16e482403bf132cfb12960d2dc2e7/pycairo-1.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:b69be8bb65c46b680771dc6a1a422b1cdd0cffb17be548f223e8cbbb6205567c", size = 694644, upload-time = "2025-11-11T19:11:48.599Z" }, { url = "https://files.pythonhosted.org/packages/f6/28/6363087b9e60af031398a6ee5c248639eefc6cc742884fa2789411b1f73b/pycairo-1.29.0-cp312-cp312-win32.whl", hash = "sha256:91bcd7b5835764c616a615d9948a9afea29237b34d2ed013526807c3d79bb1d0", size = 751486, upload-time = "2025-11-11T19:11:54.451Z" }, { url = "https://files.pythonhosted.org/packages/3a/d2/d146f1dd4ef81007686ac52231dd8f15ad54cf0aa432adaefc825475f286/pycairo-1.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:3f01c3b5e49ef9411fff6bc7db1e765f542dc1c9cfed4542958a5afa3a8b8e76", size = 845383, upload-time = "2025-11-11T19:12:01.551Z" }, { url = "https://files.pythonhosted.org/packages/01/16/6e6f33bb79ec4a527c9e633915c16dc55a60be26b31118dbd0d5859e8c51/pycairo-1.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:eafe3d2076f3533535ad4a361fa0754e0ee66b90e548a3a0f558fed00b1248f2", size = 694518, upload-time = "2025-11-11T19:12:06.561Z" }, @@ -4611,7 +4333,7 @@ name = "pytest-cov" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage", extra = ["toml"], marker = "platform_python_implementation != 'PyPy'" }, + { name = "coverage", marker = "platform_python_implementation != 'PyPy'" }, { name = "pluggy", marker = "platform_python_implementation != 'PyPy'" }, { name = "pytest", marker = "platform_python_implementation != 'PyPy'" }, ] @@ -4807,20 +4529,6 @@ version = "0.6.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ed/e3/c0c8bf6fca79ac946a28d57f116e3b9e5b10a4469b6f70bf73f3744c49bf/python_bidi-0.6.7.tar.gz", hash = "sha256:c10065081c0e137975de5d9ba2ff2306286dbf5e0c586d4d5aec87c856239b41", size = 45503, upload-time = "2025-10-22T09:52:49.624Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/de/c30a13ad95239507af472a5fc2cadd2e5e172055068f12ac39b37922c7f8/python_bidi-0.6.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a8892a7da0f617135fe9c92dc7070d13a0f96ab3081f9db7ff5b172a3905bd78", size = 274420, upload-time = "2025-10-22T09:51:58.262Z" }, - { url = "https://files.pythonhosted.org/packages/ad/9f/be5efef7eea5f1e2a6415c4052a988f594dcf5a11a15103f2718d324a35b/python_bidi-0.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:06650a164e63e94dc8a291cc9d415b4027cb1cce125bc9b02dac0f34d535ed47", size = 264586, upload-time = "2025-10-22T09:51:49.255Z" }, - { url = "https://files.pythonhosted.org/packages/87/ec/2c374b6de35870817ffb3512c0666ea8c3794ef923b5586c69451e0e5395/python_bidi-0.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6df7be07af867ec1d121c92ea827efad4d77b25457c06eeab477b601e82b2340", size = 293672, upload-time = "2025-10-22T09:50:58.504Z" }, - { url = "https://files.pythonhosted.org/packages/29/1a/722d7d7128bdc9a530351a0d2fdf2ff5f4af66a865a6bca925f99832e2cc/python_bidi-0.6.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73a88dc333efc42281bd800d5182c8625c6e11d109fc183fe3d7a11d48ab1150", size = 302643, upload-time = "2025-10-22T09:51:06.419Z" }, - { url = "https://files.pythonhosted.org/packages/24/d7/5b9b593dd58fc745233d8476e9f4e0edd437547c78c58340619868470349/python_bidi-0.6.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f24189dc3aea3a0a94391a047076e1014306b39ba17d7a38ebab510553cd1a97", size = 441692, upload-time = "2025-10-22T09:51:15.39Z" }, - { url = "https://files.pythonhosted.org/packages/08/b9/16e7a1db5f022da6654e89875d231ec2e044d42ef7b635feeff61cee564c/python_bidi-0.6.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a507fe6928a27a308e04ebf2065719b7850d1bf9ff1924f4e601ef77758812bd", size = 326933, upload-time = "2025-10-22T09:51:23.631Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a6/45aaec301292c6a07a9cc3168f5d1a92c8adc2ef36a3cd1f227b9caa980c/python_bidi-0.6.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbbffb948a32f9783d1a28bc0c53616f0a76736ed1e7c1d62e3e99a8dfaab869", size = 302034, upload-time = "2025-10-22T09:51:41.347Z" }, - { url = "https://files.pythonhosted.org/packages/71/a3/7e42cce6e153c21b4e5cc96d429a5910909823f6fedd174b64ff67bc76a7/python_bidi-0.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7e507e1e798ebca77ddc9774fd405107833315ad802cfdaa1ab07b6d9154fc8", size = 315738, upload-time = "2025-10-22T09:51:33.409Z" }, - { url = "https://files.pythonhosted.org/packages/43/7c/a5e4c0acc8e6ca61953b4add0576f0483f63b809b5389154e5da13927b0b/python_bidi-0.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:849a57d39feaf897955d0b19bbf4796bea53d1bcdf83b82e0a7b059167eb2049", size = 473968, upload-time = "2025-10-22T09:52:07.624Z" }, - { url = "https://files.pythonhosted.org/packages/b1/aa/a18bc3cbab7a0e598cbe7b89f2c0913aedcc66dcafce9a4c357465c87859/python_bidi-0.6.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5ebc19f24e65a1f5c472e26d88e78b9d316e293bc6f205f32de4c4e99276336e", size = 567038, upload-time = "2025-10-22T09:52:18.594Z" }, - { url = "https://files.pythonhosted.org/packages/92/46/fc6c54a8b5bfbee50e650f885ddef4f8c4f92880467ea0bc2bf133747048/python_bidi-0.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:24388c77cb00b8aa0f9c84beb7e3e523a3dac4f786ece64a1d8175a07b24da72", size = 493970, upload-time = "2025-10-22T09:52:29.815Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f1/2c15f5b938b2e087e4e950cc14dcead5bedbaabfc6c576dac15739bc0c91/python_bidi-0.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:19737d217088ef27014f98eac1827c5913e6fb1dea96332ed84ede61791070d9", size = 465161, upload-time = "2025-10-22T09:52:40.517Z" }, - { url = "https://files.pythonhosted.org/packages/56/d7/73a70a1fb819152485521b8dfe627e14ba9d3d5a65213244ab099adf3600/python_bidi-0.6.7-cp311-cp311-win32.whl", hash = "sha256:95c9de7ebc55ffb777548f2ecaf4b96b0fa0c92f42bf4d897b9f4cd164ec7394", size = 157033, upload-time = "2025-10-22T09:52:59.228Z" }, - { url = "https://files.pythonhosted.org/packages/68/84/06999dc54ea047fe33209af7150df4202ab7ad52deeb66b2c2040ac07884/python_bidi-0.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:898db0ea3e4aaa95b7fecba02a7560dfbf368f9d85053f2875f6d610c4d4ec2c", size = 161282, upload-time = "2025-10-22T09:52:51.467Z" }, { url = "https://files.pythonhosted.org/packages/e5/03/5b2f3e73501d0f41ebc2b075b49473047c6cdfc3465cf890263fc69e3915/python_bidi-0.6.7-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:11c51579e01f768446a7e13a0059fea1530936a707abcbeaad9467a55cb16073", size = 272536, upload-time = "2025-10-22T09:51:59.721Z" }, { url = "https://files.pythonhosted.org/packages/31/77/c6048e938a73e5a7c6fa3d5e3627a5961109daa728c2e7d050567cecdc26/python_bidi-0.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47deaada8949af3a790f2cd73b613f9bfa153b4c9450f91c44a60c3109a81f73", size = 263258, upload-time = "2025-10-22T09:51:50.328Z" }, { url = "https://files.pythonhosted.org/packages/57/56/ed4dc501cab7de70ce35cd435c86278e4eb1caf238c80bc72297767c9219/python_bidi-0.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b38ddfab41d10e780edb431edc30aec89bee4ce43d718e3896e99f33dae5c1d3", size = 292700, upload-time = "2025-10-22T09:50:59.628Z" }, @@ -4857,18 +4565,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/a3/50d1f6060a7a500768768f5f8735cb68deba36391248dbf13d5d2c9c0885/python_bidi-0.6.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4ea928c31c7364098f853f122868f6f2155d6840661f7ea8b2ccfdf6084eb9f4", size = 463126, upload-time = "2025-10-22T09:52:44.28Z" }, { url = "https://files.pythonhosted.org/packages/d2/47/712cd7d1068795c57fdf6c4acca00716688aa8b4e353b30de2ed8f599fd6/python_bidi-0.6.7-cp314-cp314-win32.whl", hash = "sha256:f7c055a50d068b3a924bd33a327646346839f55bcb762a26ec3fde8ea5d40564", size = 155793, upload-time = "2025-10-22T09:53:02.7Z" }, { url = "https://files.pythonhosted.org/packages/c3/e8/1f86bf699b20220578351f9b7b635ed8b6e84dd51ad3cca08b89513ae971/python_bidi-0.6.7-cp314-cp314-win_amd64.whl", hash = "sha256:8a17631e3e691eec4ae6a370f7b035cf0a5767f4457bd615d11728c23df72e43", size = 159821, upload-time = "2025-10-22T09:52:54.95Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/6135798d84b62eea70c0f9435301c2a4ba854e87be93a3fcd1d935266d24/python_bidi-0.6.7-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c9a679b24f5c6f366a0dec75745e1abeae2f597f033d0d54c74cbe62e7e6ae28", size = 276275, upload-time = "2025-10-22T09:52:05.078Z" }, - { url = "https://files.pythonhosted.org/packages/74/83/2123596d43e552af9e2806e361646fa579f34a1d1e9e2c1707a0ab6a02dd/python_bidi-0.6.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:05fe5971110013610f0db40505d0b204edc756e92eafac1372a464f8b9162b11", size = 266951, upload-time = "2025-10-22T09:51:56.216Z" }, - { url = "https://files.pythonhosted.org/packages/5c/8c/8d1e1501717227a6d52fc7b9c47a3de61486b024fbdd4821bfad724c0699/python_bidi-0.6.7-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17572944e6d8fb616d111fc702c759da2bf7cedab85a3e4fa2af0c9eb95ed438", size = 295745, upload-time = "2025-10-22T09:51:04.438Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ff/ef04e7f9067c2c5d862b9f8d9a192486c500c8aa295f0fb756c25ab47fc8/python_bidi-0.6.7-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3b63d19f3f56ff7f99bce5ca9ef8c811dbf0f509d8e84c1bc06105ed26a49528", size = 304123, upload-time = "2025-10-22T09:51:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/be/72/b973895e257a7d4cc8365ab094612f6ee885df863a4964d8865b9f534b67/python_bidi-0.6.7-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1350033431d75be749273236dcfc808e54404cd6ece6204cdb1bc4ccc163455", size = 442484, upload-time = "2025-10-22T09:51:21.575Z" }, - { url = "https://files.pythonhosted.org/packages/c1/1a/68ca9d10bc309828e8cdb2d57a30dd7e5753ac8520c8d7a0322daeb9eef7/python_bidi-0.6.7-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c5fb99f774748de283fadf915106f130b74be1bade934b7f73a7a8488b95da1", size = 329149, upload-time = "2025-10-22T09:51:31.232Z" }, - { url = "https://files.pythonhosted.org/packages/03/40/ab450c06167a7de596d99b1ba5cee2c605b3ff184baccf08210ede706b1b/python_bidi-0.6.7-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d28e2bdcadf5b6161bb4ee9313ce41eac746ba57e744168bf723a415a11af05", size = 303529, upload-time = "2025-10-22T09:51:46.997Z" }, - { url = "https://files.pythonhosted.org/packages/ec/c5/585b5c413e3b77a32500fb877ea30aa23c45a6064dbd7fe77d87b72cd90b/python_bidi-0.6.7-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3777ae3e088e94df854fbcbd8d59f9239b74aac036cb6bbd19f8035c8e42478", size = 317753, upload-time = "2025-10-22T09:51:39.272Z" }, - { url = "https://files.pythonhosted.org/packages/f9/05/b7b4b447890d614ccb40633f4d65f334bcf9fe3ad13be33aaa54dcbc34f3/python_bidi-0.6.7-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:77bb4cbadf4121db395189065c58c9dd5d1950257cc1983004e6df4a3e2f97ad", size = 476054, upload-time = "2025-10-22T09:52:15.856Z" }, - { url = "https://files.pythonhosted.org/packages/ca/94/64f6d2c09c4426918345b54ca8902f94b663eadd744c9dd89070f546c9bc/python_bidi-0.6.7-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:f1fe71c203f66bc169a393964d5702f9251cfd4d70279cb6453fdd42bd2e675f", size = 568365, upload-time = "2025-10-22T09:52:27.556Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d2/c39a6b82aa0fcedac7cbe6078b78bb9089b43d903f8e00859e42b504bb8e/python_bidi-0.6.7-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:d87ed09e5c9b6d2648e8856a4e556147b9d3cd4d63905fa664dd6706bc414256", size = 495292, upload-time = "2025-10-22T09:52:38.306Z" }, - { url = "https://files.pythonhosted.org/packages/0a/8d/a80f37ab92118e305d7b574306553599f81534c50b4eb23ef34ebe09c09c/python_bidi-0.6.7-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:766d5f5a686eb99b53168a7bdfb338035931a609bdbbcb537cef9e050a86f359", size = 467159, upload-time = "2025-10-22T09:52:48.603Z" }, ] [[package]] @@ -4945,9 +4641,6 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, @@ -4974,15 +4667,6 @@ 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/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" }, @@ -5041,21 +4725,6 @@ version = "3.14.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ed/fc/a98b616db9a42dcdda7c78c76bdfdf6fe290ac4c5ffbb186f73ec981ad5b/rapidfuzz-3.14.1.tar.gz", hash = "sha256:b02850e7f7152bd1edff27e9d584505b84968cacedee7a734ec4050c655a803c", size = 57869570, upload-time = "2025-09-08T21:08:15.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/c7/c3c860d512606225c11c8ee455b4dc0b0214dbcfac90a2c22dddf55320f3/rapidfuzz-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d976701060886a791c8a9260b1d4139d14c1f1e9a6ab6116b45a1acf3baff67", size = 1938398, upload-time = "2025-09-08T21:05:44.031Z" }, - { url = "https://files.pythonhosted.org/packages/c0/f3/67f5c5cd4d728993c48c1dcb5da54338d77c03c34b4903cc7839a3b89faf/rapidfuzz-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e6ba7e6eb2ab03870dcab441d707513db0b4264c12fba7b703e90e8b4296df2", size = 1392819, upload-time = "2025-09-08T21:05:45.549Z" }, - { url = "https://files.pythonhosted.org/packages/d5/06/400d44842f4603ce1bebeaeabe776f510e329e7dbf6c71b6f2805e377889/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e532bf46de5fd3a1efde73a16a4d231d011bce401c72abe3c6ecf9de681003f", size = 1391798, upload-time = "2025-09-08T21:05:47.044Z" }, - { url = "https://files.pythonhosted.org/packages/90/97/a6944955713b47d88e8ca4305ca7484940d808c4e6c4e28b6fa0fcbff97e/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f9b6a6fb8ed9b951e5f3b82c1ce6b1665308ec1a0da87f799b16e24fc59e4662", size = 1699136, upload-time = "2025-09-08T21:05:48.919Z" }, - { url = "https://files.pythonhosted.org/packages/a8/1e/f311a5c95ddf922db6dd8666efeceb9ac69e1319ed098ac80068a4041732/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b6ac3f9810949caef0e63380b11a3c32a92f26bacb9ced5e32c33560fcdf8d1", size = 2236238, upload-time = "2025-09-08T21:05:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/85/27/e14e9830255db8a99200f7111b158ddef04372cf6332a415d053fe57cc9c/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e52e4c34fd567f77513e886b66029c1ae02f094380d10eba18ba1c68a46d8b90", size = 3183685, upload-time = "2025-09-08T21:05:52.362Z" }, - { url = "https://files.pythonhosted.org/packages/61/b2/42850c9616ddd2887904e5dd5377912cbabe2776fdc9fd4b25e6e12fba32/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:2ef72e41b1a110149f25b14637f1cedea6df192462120bea3433980fe9d8ac05", size = 1231523, upload-time = "2025-09-08T21:05:53.927Z" }, - { url = "https://files.pythonhosted.org/packages/de/b5/6b90ed7127a1732efef39db46dd0afc911f979f215b371c325a2eca9cb15/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fb654a35b373d712a6b0aa2a496b2b5cdd9d32410cfbaecc402d7424a90ba72a", size = 2415209, upload-time = "2025-09-08T21:05:55.422Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/af51c50d238c82f2179edc4b9f799cc5a50c2c0ebebdcfaa97ded7d02978/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2b2c12e5b9eb8fe9a51b92fe69e9ca362c0970e960268188a6d295e1dec91e6d", size = 2532957, upload-time = "2025-09-08T21:05:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/50/92/29811d2ba7c984251a342c4f9ccc7cc4aa09d43d800af71510cd51c36453/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4f069dec5c450bd987481e752f0a9979e8fdf8e21e5307f5058f5c4bb162fa56", size = 2815720, upload-time = "2025-09-08T21:05:58.618Z" }, - { url = "https://files.pythonhosted.org/packages/78/69/cedcdee16a49e49d4985eab73b59447f211736c5953a58f1b91b6c53a73f/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4d0d9163725b7ad37a8c46988cae9ebab255984db95ad01bf1987ceb9e3058dd", size = 3323704, upload-time = "2025-09-08T21:06:00.576Z" }, - { url = "https://files.pythonhosted.org/packages/76/3e/5a3f9a5540f18e0126e36f86ecf600145344acb202d94b63ee45211a18b8/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db656884b20b213d846f6bc990c053d1f4a60e6d4357f7211775b02092784ca1", size = 4287341, upload-time = "2025-09-08T21:06:02.301Z" }, - { url = "https://files.pythonhosted.org/packages/46/26/45db59195929dde5832852c9de8533b2ac97dcc0d852d1f18aca33828122/rapidfuzz-3.14.1-cp311-cp311-win32.whl", hash = "sha256:4b42f7b9c58cbcfbfaddc5a6278b4ca3b6cd8983e7fd6af70ca791dff7105fb9", size = 1726574, upload-time = "2025-09-08T21:06:04.357Z" }, - { url = "https://files.pythonhosted.org/packages/01/5c/a4caf76535f35fceab25b2aaaed0baecf15b3d1fd40746f71985d20f8c4b/rapidfuzz-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:e5847f30d7d4edefe0cb37294d956d3495dd127c1c56e9128af3c2258a520bb4", size = 1547124, upload-time = "2025-09-08T21:06:06.002Z" }, - { url = "https://files.pythonhosted.org/packages/c6/66/aa93b52f95a314584d71fa0b76df00bdd4158aafffa76a350f1ae416396c/rapidfuzz-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:5087d8ad453092d80c042a08919b1cb20c8ad6047d772dc9312acd834da00f75", size = 816958, upload-time = "2025-09-08T21:06:07.509Z" }, { url = "https://files.pythonhosted.org/packages/df/77/2f4887c9b786f203e50b816c1cde71f96642f194e6fa752acfa042cf53fd/rapidfuzz-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:809515194f628004aac1b1b280c3734c5ea0ccbd45938c9c9656a23ae8b8f553", size = 1932216, upload-time = "2025-09-08T21:06:09.342Z" }, { url = "https://files.pythonhosted.org/packages/de/bd/b5e445d156cb1c2a87d36d8da53daf4d2a1d1729b4851660017898b49aa0/rapidfuzz-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0afcf2d6cb633d0d4260d8df6a40de2d9c93e9546e2c6b317ab03f89aa120ad7", size = 1393414, upload-time = "2025-09-08T21:06:10.959Z" }, { url = "https://files.pythonhosted.org/packages/de/bd/98d065dd0a4479a635df855616980eaae1a1a07a876db9400d421b5b6371/rapidfuzz-3.14.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1c3d07d53dcafee10599da8988d2b1f39df236aee501ecbd617bd883454fcd", size = 1377194, upload-time = "2025-09-08T21:06:12.471Z" }, @@ -5111,9 +4780,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/ef/6fd10aa028db19c05b4ac7fe77f5613e4719377f630c709d89d7a538eea2/rapidfuzz-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:93b6294a3ffab32a9b5f9b5ca048fa0474998e7e8bb0f2d2b5e819c64cb71ec7", size = 1795851, upload-time = "2025-09-08T21:07:55.76Z" }, { url = "https://files.pythonhosted.org/packages/e4/30/acd29ebd906a50f9e0f27d5f82a48cf5e8854637b21489bd81a2459985cf/rapidfuzz-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6cb56b695421538fdbe2c0c85888b991d833b8637d2f2b41faa79cea7234c000", size = 1626748, upload-time = "2025-09-08T21:07:58.166Z" }, { url = "https://files.pythonhosted.org/packages/c1/f4/dfc7b8c46b1044a47f7ca55deceb5965985cff3193906cb32913121e6652/rapidfuzz-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7cd312c380d3ce9d35c3ec9726b75eee9da50e8a38e89e229a03db2262d3d96b", size = 853771, upload-time = "2025-09-08T21:08:00.816Z" }, - { url = "https://files.pythonhosted.org/packages/05/c7/1b17347e30f2b50dd976c54641aa12003569acb1bdaabf45a5cc6f471c58/rapidfuzz-3.14.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4a21ccdf1bd7d57a1009030527ba8fae1c74bf832d0a08f6b67de8f5c506c96f", size = 1862602, upload-time = "2025-09-08T21:08:09.088Z" }, - { url = "https://files.pythonhosted.org/packages/09/cf/95d0dacac77eda22499991bd5f304c77c5965fb27348019a48ec3fe4a3f6/rapidfuzz-3.14.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:589fb0af91d3aff318750539c832ea1100dbac2c842fde24e42261df443845f6", size = 1339548, upload-time = "2025-09-08T21:08:11.059Z" }, - { url = "https://files.pythonhosted.org/packages/b6/58/f515c44ba8c6fa5daa35134b94b99661ced852628c5505ead07b905c3fc7/rapidfuzz-3.14.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a4f18092db4825f2517d135445015b40033ed809a41754918a03ef062abe88a0", size = 1513859, upload-time = "2025-09-08T21:08:13.07Z" }, ] [[package]] @@ -5122,12 +4788,6 @@ version = "1.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/81/af/c9654b4f9b054ec163ed7cb20d8db0e5ae05e2e9ce99a4c11d91a2180b3f/rcssmin-1.2.2.tar.gz", hash = "sha256:806986eaf7414545edc28a1d29523e9560e49e151ff4a337d9d1f0271d6e1cc4", size = 587012, upload-time = "2025-10-12T10:48:08.932Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/3b/d4db4a2fb0d0033d222b56526bb1935e892e2560516b0378b4ccffec8d9c/rcssmin-1.2.2-cp311-cp311-manylinux1_i686.whl", hash = "sha256:da4801f4f429d66f9922871a7c71dee54c87f0ea5666cae6f1eb84c3fbc4e1f4", size = 49090, upload-time = "2025-10-12T10:48:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/bd/f3/aeed5758339ccba61a82de12897762bad8f4317883a20de2dcc78842afda/rcssmin-1.2.2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:e6b5913f3e8cb249044e916bc6ddcb5158815121548686a0fc8e2b8a5961a62e", size = 49368, upload-time = "2025-10-12T10:48:26.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7b/e4206002c8c1bdcac6905ad7b200d62662d20d2b23f3d3e7df4e89447fdd/rcssmin-1.2.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:1472a98142d10d6c6772d96424ddcaf99d7e1d3217475f7f28f7d40dd84f24a2", size = 50714, upload-time = "2025-10-12T10:48:27.305Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/e1cd335ce659af50a2c16dad37eee4b166536d73a463cdfeab5bb8e0833b/rcssmin-1.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6c91878a7e6f708f90c1bbc1a02729f45b2e5dee89045b395e997aa71744ee4", size = 53376, upload-time = "2025-10-12T10:48:28.374Z" }, - { url = "https://files.pythonhosted.org/packages/98/0b/9071882a74df398bf40e668a89cf2dd7eb95ff2e02c111c4c156aaad745a/rcssmin-1.2.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:844227668a235451eb544455b911067ba5495d680857d4bad2b0b78878f30a5c", size = 53194, upload-time = "2025-10-12T10:48:29.331Z" }, - { url = "https://files.pythonhosted.org/packages/03/48/295e57fbb4767226b5231ee99c056eab5447845259e4172e2db76f07c26d/rcssmin-1.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc866e23121bc4e29014b588fb67c8242a80ce053196f511c4c806b30ca6a393", size = 52998, upload-time = "2025-10-12T10:48:30.266Z" }, { url = "https://files.pythonhosted.org/packages/86/5c/29af37ffb21a3069d108902868262b25fbbf731821cf5c7de76bba986dd1/rcssmin-1.2.2-cp312-cp312-manylinux1_i686.whl", hash = "sha256:78249189d39344a1e9d813c51362831537500e104c5bdce4ff24fe59010e9ee1", size = 48819, upload-time = "2025-10-12T10:48:31.3Z" }, { url = "https://files.pythonhosted.org/packages/89/dc/b522a5e1a0a8ef8af50adbb3bdd9f5a059a9890b9fc5ce3f44a37a996a74/rcssmin-1.2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:217efed0dff304d503bf481068ddb13ae72176ed5970f1011fb1a1e379308d9c", size = 49248, upload-time = "2025-10-12T10:48:32.254Z" }, { url = "https://files.pythonhosted.org/packages/66/0b/7c0018793080ed26939d9beaf09591cf58fb9cda3253a891137b841a902a/rcssmin-1.2.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:c51aa47b1752ae55ad4cf4332e7316c5206a6a686d65bc15431a6bfea393e665", size = 50723, upload-time = "2025-10-12T10:48:33.398Z" }, @@ -5178,9 +4838,6 @@ wheels = [ name = "redis" version = "7.4.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-timeout", marker = "python_full_version < '3.11.3' and platform_python_implementation != 'PyPy'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/7b/7f/3759b1d0d72b7c92f0d70ffd9dc962b7b7b5ee74e135f9d7d8ab06b8a318/redis-7.4.0.tar.gz", hash = "sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad", size = 4943913, upload-time = "2026-03-24T09:14:37.53Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" }, @@ -5192,22 +4849,6 @@ version = "2026.5.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/dc/c1f2df4027e82fc54b5a473e4b250f5139faca49a0fbe29a48668d228f34/regex-2026.5.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ccf5249114cc3e772ecdd88a98a86eca0fd74c61ce32a94743758c083fc05d48", size = 489445, upload-time = "2026-05-09T23:12:06.111Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/59f01110660081cce9c0bc30ebd0b5ee250dacf658e3248ed92f01e0e8ee/regex-2026.5.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8", size = 291271, upload-time = "2026-05-09T23:12:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/58/b6/14b2c84ff90ddb370c81d27503f4a0fcf071496416f4855f6cc8c5d81c35/regex-2026.5.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef31cbfe458e21c6122ba8150ff060e0c7789ed0d26eb423f25472584920b555", size = 289212, upload-time = "2026-05-09T23:12:09.266Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/4db86529117320de0c84afd90e70bb47434625875e34fcef9d8c127c5b16/regex-2026.5.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:992604d02e6d9c6d786c24a706a71ecffe1020fc1ef264044474cd81fa2c3919", size = 792310, upload-time = "2026-05-09T23:12:11.416Z" }, - { url = "https://files.pythonhosted.org/packages/07/78/fe4800cd322f862ecffd2d553409b20d80650e5ed71b9d178f853d020b82/regex-2026.5.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9411dd64ca95477225734a93dfc8583b51916b8d5942f99d6cac21e09965451", size = 861721, upload-time = "2026-05-09T23:12:13.681Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d0/b3618a895dd8feb897c61bb2954edd265e1767d82a01d53065d5871127a3/regex-2026.5.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4a3ff360dfb836fecdb93a4598f9d6e2ac81e3e397125145c6221bf58cf4c", size = 906460, upload-time = "2026-05-09T23:12:15.443Z" }, - { url = "https://files.pythonhosted.org/packages/33/6f/1481597e859ef19508b345eec4afd1416ed6e6b459c75a64026ef193aecf/regex-2026.5.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a661a7d270a61f7cf460caee8b9fa2d5ef9e5c681234bcb9e0fe14f488e7dfc", size = 799843, upload-time = "2026-05-09T23:12:16.892Z" }, - { url = "https://files.pythonhosted.org/packages/73/59/955734c803f59108deccba3597ae440c76b62a652733c0006e6243758420/regex-2026.5.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f079e50a0d3cc3cd5091fa9ff45869a2e6b2cd35895731edafb0327901a8d86d", size = 773610, upload-time = "2026-05-09T23:12:19.127Z" }, - { url = "https://files.pythonhosted.org/packages/68/8f/70c04a236d651c81881dac42ef8538bddda6121434509d0a22d9e601503b/regex-2026.5.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ebe8f0b5ec5a5024dc4a4c59f444c4e9afc5f2abdbb8962065b75d27fb971f9", size = 781645, upload-time = "2026-05-09T23:12:20.806Z" }, - { url = "https://files.pythonhosted.org/packages/1d/96/05c7434d88185e5d27fe54aeb74df86bd77cd79f52f0b4eae54faa8fea70/regex-2026.5.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:97cf3bc1b7d7d2306772ec07366c80d9df00ff79e79cea32898883a646d2fae2", size = 854473, upload-time = "2026-05-09T23:12:22.465Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/6e3d8202d981f3117004bf341ee74893ba4ba8a9fbaf4b94615846550a08/regex-2026.5.9-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f9eede6a5cbdc02d4978090186390936e1776a7d1359b21e41014c609880bcf", size = 763311, upload-time = "2026-05-09T23:12:24.351Z" }, - { url = "https://files.pythonhosted.org/packages/93/c7/e7737f1526b3fb32bd4c337fd6c71c3ebb5c8296fc34d11197e0955d2e35/regex-2026.5.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:01f0f5f55f4b64dacec85dc116d3c05fd23ad3ff037bbc73a2085775953c2611", size = 844593, upload-time = "2026-05-09T23:12:26.341Z" }, - { url = "https://files.pythonhosted.org/packages/a5/27/0daffb1a535bb39f422c3d200f4ab023c71110ad66a32b366bee708baba0/regex-2026.5.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1268eddd8486dc561d08eee1156e40aa3a8fe10f4bdec8fa653b455fcbffd12c", size = 789167, upload-time = "2026-05-09T23:12:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/ce/fc/294fe4fac4f2ed67207b17471815870c1c45b3a489e08e0ac96daea16ef6/regex-2026.5.9-cp311-cp311-win32.whl", hash = "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994", size = 266249, upload-time = "2026-05-09T23:12:30.141Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b0/8dce459f6245bcf8f6e9f23ac9569f1a0f15c131cc0745e82b43226204cf/regex-2026.5.9-cp311-cp311-win_amd64.whl", hash = "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b", size = 278423, upload-time = "2026-05-09T23:12:31.676Z" }, - { url = "https://files.pythonhosted.org/packages/db/8d/f9aeff6ad63a3ef720386f2907e6d34a35a510a6e498ebad28b0fb3f6ab6/regex-2026.5.9-cp311-cp311-win_arm64.whl", hash = "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046", size = 270420, upload-time = "2026-05-09T23:12:33.194Z" }, { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, @@ -5371,12 +5012,6 @@ version = "1.2.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/59/16/14288d309d0f42c6586440c47bf6ec1a880218f698f30293fa3782db4008/rjsmin-1.2.5.tar.gz", hash = "sha256:a3f8040b0273dec773e0e807e86a4d0a9535516c0a0a35aa1bb6de6e15bb1f09", size = 427399, upload-time = "2025-10-12T10:50:27.422Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/83/30c8a74c3f837d22ac14a20da562f2922cda87228cb88553dbe967f10d89/rjsmin-1.2.5-cp311-cp311-manylinux1_i686.whl", hash = "sha256:82bac9710030b61dd1cf442724431d29b1fec7cd708c541cb2042e38763fd610", size = 31978, upload-time = "2025-10-12T10:50:42.312Z" }, - { url = "https://files.pythonhosted.org/packages/84/7c/e215e4e52f4b0f354731bd808292c5cb01c2eeba8cb310e3f099ab97d479/rjsmin-1.2.5-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:5626644872f3ad10b8334ec3383aad0906d36a085c04c608a400ed30be4d03a4", size = 31782, upload-time = "2025-10-12T10:50:43.471Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e7/d5590391d2c98389ab119e4500a6d96cf6174159295d9a2cc34dec2eb73d/rjsmin-1.2.5-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:2d0b8aaa98e51c8ae176b9a94e91f19d3043d7d328431d3d2c459b57a90c0c87", size = 32369, upload-time = "2025-10-12T10:50:45.222Z" }, - { url = "https://files.pythonhosted.org/packages/7d/ad/81bcfe46cf42ea3c8a0b9505654f413c06932c8ea43556b83404a016ddb6/rjsmin-1.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0df172044912ca2f5f04c711ded75c784fba8dc6c7a1f7f831ac831562102aa2", size = 36091, upload-time = "2025-10-12T10:50:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9e/833455223063a52ee0b0aa2cef44080677db840d9fbae5c78f027547af5a/rjsmin-1.2.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a9208911d2f04dc3bec33df7486dbd7ecfc900b0d1ead9841bbd94a382f33f00", size = 36232, upload-time = "2025-10-12T10:50:47.277Z" }, - { url = "https://files.pythonhosted.org/packages/3c/21/e4ffb7b5c3313f9d5137867f113ec9241b84e50e1d69ce979efdbffe07ed/rjsmin-1.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8b7cf8ce9022d381bfa700ae116e5f78698f486558a0fe23c57f158ba3229629", size = 36168, upload-time = "2025-10-12T10:50:48.611Z" }, { url = "https://files.pythonhosted.org/packages/d6/f3/143727b02b5c5fdc08335be8b2184f19b762ee7d184bb4459e94ed668ae0/rjsmin-1.2.5-cp312-cp312-manylinux1_i686.whl", hash = "sha256:d8b6ddaaa78fd2d3243da11c13033946d211d37729c64814cefe32dba02d9921", size = 31838, upload-time = "2025-10-12T10:50:49.553Z" }, { url = "https://files.pythonhosted.org/packages/13/dc/72ca27c526925e88e273c3af6848777b289e4eb0854afcd7c6dbbfd4d196/rjsmin-1.2.5-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:2f46270969613de2292a7f747c31cabd9354cc49f6cd23f9cc8688d3af9f889e", size = 31795, upload-time = "2025-10-12T10:50:50.459Z" }, { url = "https://files.pythonhosted.org/packages/49/1e/f8bfe2f6949b31adb66563ceca84d9d38f32867aad303cf4311b12534487/rjsmin-1.2.5-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:6e7b1bb52665894d8cba84144ee91723475948d5d1a54d7f0b25a1cdce8c5921", size = 32085, upload-time = "2025-10-12T10:50:51.704Z" }, @@ -5514,17 +5149,6 @@ version = "4.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0e/2a/54837395a3487c725669428d513293612a48d82b95a0642c936932e5d898/simplejson-4.1.1.tar.gz", hash = "sha256:c08eb9f7a90f77ae470e19a07472e9a79ebc0d1c2315d86a72767665bd5ba79f", size = 118860, upload-time = "2026-04-24T19:24:59.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/25/39013ffe279d90093ec1c848565b3683c586906c10fa55d9000ec29d046b/simplejson-4.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2867c64d92abd1992c15666fae198203093f593e43d6b81adf176bae530d493a", size = 111538, upload-time = "2026-04-24T19:22:49.051Z" }, - { url = "https://files.pythonhosted.org/packages/f2/ae/2c272971c8a87e2539c54a98eb6ff037bee1e2e93943c3986cf7500a4f3a/simplejson-4.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c47c46e16c8ea9e4850061e6ed5aa2b9cd2074cb2274bfd9c138cba15ce7453", size = 90594, upload-time = "2026-04-24T19:22:50.408Z" }, - { url = "https://files.pythonhosted.org/packages/4e/a2/6eebfb99dedc139f549200f61ade6d1890ac5707c5d427bdfa6fe39c9313/simplejson-4.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e294e33dbf316a9bbdd4030d46503c9b0f19470ae7ad6af5bae6c426bc2e869f", size = 90718, upload-time = "2026-04-24T19:22:51.694Z" }, - { url = "https://files.pythonhosted.org/packages/80/7e/c9e6c0c4ad8415e64dad0c47f619b556b02680a41631b4dbc281d55dc54d/simplejson-4.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7ce252b28fddbdd83db5bd7d93dad2a8a591d7ada098afec9c1b23d6b722a7a4", size = 180901, upload-time = "2026-04-24T19:22:53.025Z" }, - { url = "https://files.pythonhosted.org/packages/34/09/69e331e3994b1ed9be6ce9ace4ade704e7ed503edf869929ca7bb404eda8/simplejson-4.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c44ef6b02a4eb67ed17a72342341792149b3ff46f15426c26e970e49addf327", size = 178133, upload-time = "2026-04-24T19:22:54.574Z" }, - { url = "https://files.pythonhosted.org/packages/5d/40/ed806f24afef295c1032448f5ff6f6f2979392d5645ddb9f4fed7f38194d/simplejson-4.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82bfca2b85a34178c25829c703f0a9e9f113a5af7539285bd3efb583a0bf1ba3", size = 188155, upload-time = "2026-04-24T19:22:56.044Z" }, - { url = "https://files.pythonhosted.org/packages/38/94/8d6f515b827b0f7881a49c8c1ac6920b7ae9428939ef04238c973278b42a/simplejson-4.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e4b23f71dd781f8830f1663dc01a4944d3dbf87a1f93d78fba1cf64722d0ccf", size = 176225, upload-time = "2026-04-24T19:22:57.981Z" }, - { url = "https://files.pythonhosted.org/packages/c9/fd/6dffb4956563d48bbe46b91ff341adae34920e94008fd6b8d728072abfc7/simplejson-4.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:82fee635d7b73ad801030b05a75fbd34a098da0c2ecf600667a03636d09e1e42", size = 185535, upload-time = "2026-04-24T19:22:59.618Z" }, - { url = "https://files.pythonhosted.org/packages/de/d2/a509ee37763e79aec75d68f8521db1440306edeba3b8b4064ab4ee8bf1d9/simplejson-4.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:68e62eda21192c5ea9bb92d571ca46a4477fef48762f50d433de2b4253051551", size = 179302, upload-time = "2026-04-24T19:23:01.324Z" }, - { url = "https://files.pythonhosted.org/packages/d8/23/5b343bfd2a79d3b6818e4db3586c405a001a090d4c89d336e31273ce7177/simplejson-4.1.1-cp311-cp311-win32.whl", hash = "sha256:ffd3d82294b47f5ec64050021ace95fd62628a0c1cc8bbf4d06d2d1fb697e055", size = 88408, upload-time = "2026-04-24T19:23:02.808Z" }, - { url = "https://files.pythonhosted.org/packages/38/04/df9b37aedbd524dca20840d25ebe01d6ae486b89792aeff5d15b9c4114f7/simplejson-4.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:78a3fe0995be42bed62a26aa78e0e0b4d87c6545785346b9cc898f3389569a35", size = 90526, upload-time = "2026-04-24T19:23:04.408Z" }, { url = "https://files.pythonhosted.org/packages/60/25/e90998fe8e480eb43b966c09e835379887d427567ebd496563d3b1e16b19/simplejson-4.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:19040a17154dc03d289bab68d73ce0a6a0be01de30c584bbdd93490bead14b22", size = 112414, upload-time = "2026-04-24T19:23:06.084Z" }, { url = "https://files.pythonhosted.org/packages/9c/a0/abd4785f36c3400f1fbb21f517be39295a750a714f04b7ee175adf6ef580/simplejson-4.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a94ebaecdbaa80d9551a3ec6bf0c9302fc8b53ab6c1b2bfd498a1df4cb28158d", size = 91120, upload-time = "2026-04-24T19:23:07.877Z" }, { url = "https://files.pythonhosted.org/packages/b8/78/fc060d2e3b13c6ec59288574b8efac64075e316b2afba4396a56b2422f78/simplejson-4.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67341c95c0a168ab4a6d1e807e50463f1c8da932c3286d81e201266c427061fa", size = 91055, upload-time = "2026-04-24T19:23:09.264Z" }, @@ -5632,11 +5256,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ce/af/20290b55d469e873cba9d41c0206ab5461ff49d759989b3fe65010f9d265/sqlalchemy-1.4.54.tar.gz", hash = "sha256:4470fbed088c35dc20b78a39aaf4ae54fe81790c783b3264872a0224f437c31a", size = 8470350, upload-time = "2024-09-05T15:54:10.398Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/49/fb98983b5568e93696a25fd5bec1b789095b79a72d5f57c6effddaa81d0a/SQLAlchemy-1.4.54-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b05e0626ec1c391432eabb47a8abd3bf199fb74bfde7cc44a26d2b1b352c2c6e", size = 1589301, upload-time = "2024-09-05T19:22:42.197Z" }, - { url = "https://files.pythonhosted.org/packages/03/98/5a81430bbd646991346cb088a2bdc84d1bcd3dbe6b0cfc1aaa898370e5c7/SQLAlchemy-1.4.54-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13e91d6892b5fcb94a36ba061fb7a1f03d0185ed9d8a77c84ba389e5bb05e936", size = 1629553, upload-time = "2024-09-05T17:49:18.846Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/14e35db2b0d6deaa27691d014addbb0dd6f7e044f7ee465446a3c0c71404/SQLAlchemy-1.4.54-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb59a11689ff3c58e7652260127f9e34f7f45478a2f3ef831ab6db7bcd72108f", size = 1627640, upload-time = "2024-09-05T17:48:01.558Z" }, - { url = "https://files.pythonhosted.org/packages/98/62/335006a8f2c98f704f391e1a0cc01446d1b1b9c198f579f03599f55bd860/SQLAlchemy-1.4.54-cp311-cp311-win32.whl", hash = "sha256:1390ca2d301a2708fd4425c6d75528d22f26b8f5cbc9faba1ddca136671432bc", size = 1591723, upload-time = "2024-09-05T17:53:17.486Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/6b4b8c07082920f5445ec65c221fa33baab102aced5dcc2d87a15d3f8db4/SQLAlchemy-1.4.54-cp311-cp311-win_amd64.whl", hash = "sha256:2b37931eac4b837c45e2522066bda221ac6d80e78922fb77c75eb12e4dbcdee5", size = 1593511, upload-time = "2024-09-05T17:51:50.947Z" }, { url = "https://files.pythonhosted.org/packages/a5/1b/aa9b99be95d1615f058b5827447c18505b7b3f1dfcbd6ce1b331c2107152/SQLAlchemy-1.4.54-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3f01c2629a7d6b30d8afe0326b8c649b74825a0e1ebdcb01e8ffd1c920deb07d", size = 1589983, upload-time = "2024-09-05T17:39:02.132Z" }, { url = "https://files.pythonhosted.org/packages/59/47/cb0fc64e5344f0a3d02216796c342525ab283f8f052d1c31a1d487d08aa0/SQLAlchemy-1.4.54-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c24dd161c06992ed16c5e528a75878edbaeced5660c3db88c820f1f0d3fe1f4", size = 1630158, upload-time = "2024-09-05T17:50:13.255Z" }, { url = "https://files.pythonhosted.org/packages/c0/8b/f45dd378f6c97e8ff9332ff3d03ecb0b8c491be5bb7a698783b5a2f358ec/SQLAlchemy-1.4.54-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5e0d47d619c739bdc636bbe007da4519fc953393304a5943e0b5aec96c9877c", size = 1629232, upload-time = "2024-09-05T17:48:15.514Z" }, @@ -5767,60 +5386,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] -[[package]] -name = "tomli" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, - { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, - { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, - { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, - { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, - { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, - { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, - { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, - { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, - { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, - { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, - { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, - { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, - { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, - { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, - { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, - { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, - { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, - { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, - { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, -] - [[package]] name = "towncrier" version = "25.8.0" @@ -6012,12 +5577,6 @@ version = "0.21.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, - { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, @@ -6093,9 +5652,6 @@ version = "6.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, @@ -6123,19 +5679,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, - { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, - { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, - { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, - { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, - { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, - { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, - { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, - { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, @@ -6195,10 +5738,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, - { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, - { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, ] [[package]] @@ -6256,17 +5795,6 @@ version = "15.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, @@ -6337,16 +5865,6 @@ version = "1.17.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, - { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, - { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, - { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, - { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, - { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, - { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, @@ -6437,27 +5955,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, ] -[[package]] -name = "zipp" -version = "3.23.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, -] - [[package]] name = "zope-interface" version = "8.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/88/3a/7fcf02178b8fad0a51e67e32765cd039ae505d054d744d76b8c2bbcba5ba/zope_interface-8.0.1.tar.gz", hash = "sha256:eba5610d042c3704a48222f7f7c6ab5b243ed26f917e2bc69379456b115e02d1", size = 253746, upload-time = "2025-09-25T05:55:51.285Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/2f/c10c739bcb9b072090c97c2e08533777497190daa19d190d72b4cce9c7cb/zope_interface-8.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4bd01022d2e1bce4a4a4ed9549edb25393c92e607d7daa6deff843f1f68b479d", size = 207903, upload-time = "2025-09-25T05:58:21.671Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e1/9845ac3697f108d9a1af6912170c59a23732090bbfb35955fe77e5544955/zope_interface-8.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:29be8db8b712d94f1c05e24ea230a879271d787205ba1c9a6100d1d81f06c69a", size = 208345, upload-time = "2025-09-25T05:58:24.217Z" }, - { url = "https://files.pythonhosted.org/packages/f2/49/6573bc8b841cfab18e80c8e8259f1abdbbf716140011370de30231be79ad/zope_interface-8.0.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:51ae1b856565b30455b7879fdf0a56a88763b401d3f814fa9f9542d7410dbd7e", size = 255027, upload-time = "2025-09-25T05:58:19.975Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fd/908b0fd4b1ab6e412dfac9bd2b606f2893ef9ba3dd36d643f5e5b94c57b3/zope_interface-8.0.1-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d2e7596149cb1acd1d4d41b9f8fe2ffc0e9e29e2e91d026311814181d0d9efaf", size = 259800, upload-time = "2025-09-25T05:58:11.487Z" }, - { url = "https://files.pythonhosted.org/packages/dc/78/8419a2b4e88410520ed4b7f93bbd25a6d4ae66c4e2b131320f2b90f43077/zope_interface-8.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2737c11c34fb9128816759864752d007ec4f987b571c934c30723ed881a7a4f", size = 260978, upload-time = "2025-09-25T06:26:24.483Z" }, - { url = "https://files.pythonhosted.org/packages/e5/90/caf68152c292f1810e2bd3acd2177badf08a740aa8a348714617d6c9ad0b/zope_interface-8.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf66e4bf731aa7e0ced855bb3670e8cda772f6515a475c6a107bad5cb6604103", size = 212155, upload-time = "2025-09-25T05:59:40.318Z" }, { url = "https://files.pythonhosted.org/packages/dc/a6/0f08713ddda834c428ebf97b2a7fd8dea50c0100065a8955924dbd94dae8/zope_interface-8.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:115f27c1cc95ce7a517d960ef381beedb0a7ce9489645e80b9ab3cbf8a78799c", size = 208609, upload-time = "2025-09-25T05:58:53.698Z" }, { url = "https://files.pythonhosted.org/packages/e9/5e/d423045f54dc81e0991ec655041e7a0eccf6b2642535839dd364b35f4d7f/zope_interface-8.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af655c573b84e3cb6a4f6fd3fbe04e4dc91c63c6b6f99019b3713ef964e589bc", size = 208797, upload-time = "2025-09-25T05:58:56.258Z" }, { url = "https://files.pythonhosted.org/packages/c6/43/39d4bb3f7a80ebd261446792493cfa4e198badd47107224f5b6fe1997ad9/zope_interface-8.0.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:23f82ef9b2d5370750cc1bf883c3b94c33d098ce08557922a3fbc7ff3b63dfe1", size = 259242, upload-time = "2025-09-25T05:58:21.602Z" }, @@ -6478,16 +5981,6 @@ version = "0.2.3.post1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5e/7c/a8f6696e694709e2abcbccd27d05ef761e9b6efae217e11d977471555b62/zopfli-0.2.3.post1.tar.gz", hash = "sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99", size = 175629, upload-time = "2024-10-18T15:42:05.946Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/6d/c8224a8fc77c1dff6caaa2dc63794a40ea284c82ac20030fb2521092dca6/zopfli-0.2.3.post1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:518f1f4ed35dd69ce06b552f84e6d081f07c552b4c661c5312d950a0b764a58a", size = 296334, upload-time = "2024-10-18T15:40:44.684Z" }, - { url = "https://files.pythonhosted.org/packages/f8/da/df0f87a489d223f184d69e9e88c80c1314be43b2361acffefdc09659e00d/zopfli-0.2.3.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:615a8ac9dda265e9cc38b2a76c3142e4a9f30fea4a79c85f670850783bc6feb4", size = 163886, upload-time = "2024-10-18T15:40:45.812Z" }, - { url = "https://files.pythonhosted.org/packages/39/b7/14529a7ae608cedddb2f791cbc13a392a246e2e6d9c9b4b8bcda707d08d8/zopfli-0.2.3.post1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a82fc2dbebe6eb908b9c665e71496f8525c1bc4d2e3a7a7722ef2b128b6227c8", size = 823654, upload-time = "2024-10-18T15:40:46.969Z" }, - { url = "https://files.pythonhosted.org/packages/57/48/217c7bd720553d9e68b96926c02820e8b6184ef6dbac937823abad85b154/zopfli-0.2.3.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37d011e92f7b9622742c905fdbed9920a1d0361df84142807ea2a528419dea7f", size = 826188, upload-time = "2024-10-18T15:40:48.147Z" }, - { url = "https://files.pythonhosted.org/packages/2f/8b/5ab8c4c6db2564a0c3369e584090c101ffad4f9d0a39396e0d3e80c98413/zopfli-0.2.3.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e63d558847166543c2c9789e6f985400a520b7eacc4b99181668b2c3aeadd352", size = 850573, upload-time = "2024-10-18T15:40:49.481Z" }, - { url = "https://files.pythonhosted.org/packages/33/f8/f52ec5c713f3325c852f19af7c8e3f98109ddcd1ce400dc39005072a2fea/zopfli-0.2.3.post1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60db20f06c3d4c5934b16cfa62a2cc5c3f0686bffe0071ed7804d3c31ab1a04e", size = 1754164, upload-time = "2024-10-18T15:40:50.952Z" }, - { url = "https://files.pythonhosted.org/packages/92/24/6a6018125e1cc6ee5880a0ae60456fdc8a2da43f2f14b487cf49439a3448/zopfli-0.2.3.post1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:716cdbfc57bfd3d3e31a58e6246e8190e6849b7dbb7c4ce39ef8bbf0edb8f6d5", size = 1906135, upload-time = "2024-10-18T15:40:52.484Z" }, - { url = "https://files.pythonhosted.org/packages/87/ad/697521dac8b46f0e0d081a3da153687d7583f3a2cd5466af1ddb9928394f/zopfli-0.2.3.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3a89277ed5f8c0fb2d0b46d669aa0633123aa7381f1f6118c12f15e0fb48f8ca", size = 1835047, upload-time = "2024-10-18T15:40:54.453Z" }, - { url = "https://files.pythonhosted.org/packages/95/00/042c0cdba957343d7a83e572fc5ffe62de03d57c43075c8cf920b8b542e6/zopfli-0.2.3.post1-cp311-cp311-win32.whl", hash = "sha256:75a26a2307b10745a83b660c404416e984ee6fca515ec7f0765f69af3ce08072", size = 82635, upload-time = "2024-10-18T15:40:55.632Z" }, - { url = "https://files.pythonhosted.org/packages/e6/cc/07119cba00db12d7ef0472637b7d71a95f2c8e9a20ed460d759acd274887/zopfli-0.2.3.post1-cp311-cp311-win_amd64.whl", hash = "sha256:81c341d9bb87a6dbbb0d45d6e272aca80c7c97b4b210f9b6e233bf8b87242f29", size = 99345, upload-time = "2024-10-18T15:40:56.965Z" }, { url = "https://files.pythonhosted.org/packages/3f/ce/b6441cc01881d06e0b5883f32c44e7cc9772e0d04e3e59277f59f80b9a19/zopfli-0.2.3.post1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef", size = 295489, upload-time = "2024-10-18T15:40:57.96Z" }, { url = "https://files.pythonhosted.org/packages/93/f0/24dd708f00ae0a925bc5c9edae858641c80f6a81a516810dc4d21688a930/zopfli-0.2.3.post1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e", size = 163010, upload-time = "2024-10-18T15:40:59.444Z" }, { url = "https://files.pythonhosted.org/packages/65/57/0378eeeb5e3e1e83b1b0958616b2bf954f102ba5b0755b9747dafbd8cb72/zopfli-0.2.3.post1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567", size = 823649, upload-time = "2024-10-18T15:41:00.642Z" }, @@ -6516,23 +6009,6 @@ version = "0.25.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, - { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, - { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, - { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, - { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, - { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, - { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, - { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, From 8e1fb8d769753dd34bca961b7c5472e886b69fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Fri, 7 Aug 2026 10:11:02 +0200 Subject: [PATCH 03/10] chore(security): podnies floor h2 do >=4.4.1 (CVE-2026-71554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pip-audit na PR-ze zglosil h2 4.3.0 (fix w 4.4.1). CVE dotyczy tak samo gałęzi `dev` — tam h2 rowniez jest 4.3.0 — zlapane przy okazji tego PR-a, bo `dev` nie mial przebiegu pip-audit od publikacji CVE. h2 przychodzi tranzytywnie przez twisted[http2]/daphne, wiec floor ladnie pasuje do istniejacej sekcji `constraint-dependencies`. Podnosi rowniez hpack 4.1.0 -> 4.2.0. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F134BWb3YoYPQ6zjzqoqds --- pyproject.toml | 5 +++++ uv.lock | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 58f931dbb..f53f276a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -264,9 +264,14 @@ environments = ["python_version >= '3.12' and python_version < '3.15' and platfo # CVE-2026-54531, GHSA-jm82-fx9c-mx94 # pyasn1 >=0.6.4 (via pyasn1-modules/python-ldap) - PYSEC-2026-3455, # PYSEC-2026-3456, PYSEC-2026-3457 (audyt: lipiec 2026) +# h2 >=4.4.1 (via twisted[http2]/daphne) - CVE-2026-71554 +# (audyt: sierpien 2026). CVE dotyczy tak samo `dev` — +# h2 bylo tam rowniez 4.3.0; zlapane przy okazji tego PR-a, +# bo `dev` nie mial przebiegu pip-audit od publikacji CVE. constraint-dependencies = [ "bleach>=6.4.0", "daphne>=4.2.2", + "h2>=4.4.1", "pyasn1>=0.6.4", "pypdf>=6.13.3", ] diff --git a/uv.lock b/uv.lock index 82b12f86f..1cef2e2ff 100644 --- a/uv.lock +++ b/uv.lock @@ -20,6 +20,7 @@ supported-markers = [ constraints = [ { name = "bleach", specifier = ">=6.4.0" }, { name = "daphne", specifier = ">=4.2.2" }, + { name = "h2", specifier = ">=4.4.1" }, { name = "pyasn1", specifier = ">=0.6.4" }, { name = "pypdf", specifier = ">=6.13.3" }, ] @@ -2678,24 +2679,24 @@ wheels = [ [[package]] name = "h2" -version = "4.3.0" +version = "4.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "hpack", marker = "platform_python_implementation != 'PyPy'" }, { name = "hyperframe", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/85/7c366e69d84c17bb778fe41419e1fbcce3033d5b7ce29bbffff0a98b859f/h2-4.4.1.tar.gz", hash = "sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516", size = 2157281, upload-time = "2026-08-03T11:45:09.509Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/e85faf23bd72a92d1921e37d674ca56eb298a3c8be31fdecef0ff2b3aaac/h2-4.4.1-py3-none-any.whl", hash = "sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6", size = 62636, upload-time = "2026-08-03T11:44:59.164Z" }, ] [[package]] name = "hpack" -version = "4.1.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/5b/fcabf6028144a8723726318b07a32c2f3314acdff6265743cf08a344b18e/hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0", size = 51300, upload-time = "2026-06-23T18:34:46.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, + { url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" }, ] [[package]] From ee5e81a352fdf60ba3b333c44b3f110c37d9dac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Fri, 7 Aug 2026 11:04:48 +0200 Subject: [PATCH 04/10] chore(deps): zdejmij pin git z django-pbn-client (0.2.3 na PyPI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR iplweb/django-pbn-client#5 zostal zmergowany (2026-08-07 07:16Z), a 0.2.3 trafilo na PyPI (08:22Z) — z `django>=5.2` zamiast dawnego capu `django<5.3` i klasyfikatorem `Framework :: Django :: 6.1`. Wraca wiec z rejestru zamiast z galezi feature'owej. Floor podniesiony do 0.2.3, bo ponizej tej wersji Django 6.1 sie nie rozwiaze. Poza porzadkiem to takze zdjecie realnego ryzyka: pin wskazywal na commit NIEZMERGOWANEJ galezi, ktora po merge'u zwykle znika — build produkcyjny przestalby sie budowac. Dodatkowo pakiet wraca do zakresu `pip-audit`, ktory pomija zaleznosci z URL-i. Zostaje jeden tymczasowy pin git: djangorestframework (3.18.0 wciaz niewydane, PR encode/django-rest-framework#10015 otwarty). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F134BWb3YoYPQ6zjzqoqds --- pyproject.toml | 11 +++-------- uv.lock | 10 +++++++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f53f276a0..7690b6186 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,9 @@ requires-python = ">=3.12,<3.15" dependencies = [ "Django>=6.1,<6.2", # Reużywalne pakiety PBN wydzielone na PyPI (github.com/iplweb/*). - "django-pbn-client>=0.2,<0.3", + # 0.2.3 to pierwsze wydanie bez capu `django<5.3` (PR iplweb/ + # django-pbn-client#5) — poniżej niej Django 6.1 się nie rozwiąże. + "django-pbn-client>=0.2.3,<0.3", "pbn-client>=0.2.2,<0.3", "django-polish-inflection>=0.1,<0.2", "django-axes>=7.0,<9", @@ -332,13 +334,6 @@ default = true # WYCOFAĆ gdy upstream wyda release z #2156 → wróć na PyPI `>=4.5`. Tracking: #280. [tool.uv.sources] django-import-export = { git = "https://github.com/mpasternak/django-import-export.git", rev = "d6ee0d39194fee31437affbdc6fee5ce549b4b8f" } -# TYMCZASOWO: django-pbn-client na PyPI (0.2.2) ma twardy cap `django<5.3`, -# ktory blokuje Django 6.1. Pin do HEAD gałęzi feat/django-61 (PR -# iplweb/django-pbn-client#5 — luzuje pin na `Django>=5.2` i dokłada oś -# django-version 5.2/6.0/6.1 do matrycy CI). -# USUŃ i wróć na `django-pbn-client>=0.3` gdy PR #5 zostanie zmergowany -# i wydany na PyPI. -django-pbn-client = { git = "https://github.com/iplweb/django-pbn-client.git", rev = "1b91ac4f968a971815160863cc7297a17dc8712b" } # TYMCZASOWO: DRF 3.17.2 (najnowsze na PyPI, wydane 2026-08-05) wciaz robi # `from django.utils.cache import cc_delim_re`, a Django 6.1 to usunelo -> # ImportError przy imporcie rest_framework.views, czyli calego api_v1. diff --git a/uv.lock b/uv.lock index 1cef2e2ff..091d02db7 100644 --- a/uv.lock +++ b/uv.lock @@ -591,7 +591,7 @@ requires-dist = [ { name = "django-multiseek", specifier = "==0.10.2" }, { name = "django-oauth-toolkit", specifier = "==3.4.0" }, { name = "django-password-policies-iplweb", specifier = "==0.9.0" }, - { name = "django-pbn-client", git = "https://github.com/iplweb/django-pbn-client.git?rev=1b91ac4f968a971815160863cc7297a17dc8712b" }, + { name = "django-pbn-client", specifier = ">=0.2.3,<0.3" }, { name = "django-pg-baseline", specifier = ">=0.3.0" }, { name = "django-polish-inflection", specifier = ">=0.1,<0.2" }, { name = "django-querysetsequence", specifier = "==0.18" }, @@ -2008,12 +2008,16 @@ wheels = [ [[package]] name = "django-pbn-client" -version = "0.2.2" -source = { git = "https://github.com/iplweb/django-pbn-client.git?rev=1b91ac4f968a971815160863cc7297a17dc8712b#1b91ac4f968a971815160863cc7297a17dc8712b" } +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django", marker = "platform_python_implementation != 'PyPy'" }, { name = "pbn-client", marker = "platform_python_implementation != 'PyPy'" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/ad/ef/5cf6c80b523e3ad98dc7d110b8d556db61c55f31a292faee749f398dbcb5/django_pbn_client-0.2.3.tar.gz", hash = "sha256:a77ec871e8e069a4a5b7436c90e533835b3e58a566e76f82f347da6237cdd4a9", size = 15427, upload-time = "2026-08-07T08:22:39.188Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/cf/245e7257184e80ccbe97bb3b7f19800dac83e4b3a229b28c6d2067b029ce/django_pbn_client-0.2.3-py3-none-any.whl", hash = "sha256:98d2a6310792fb9ee09d99e515fe28e889f486bd416e8273a26ccaa7bc84f419", size = 11276, upload-time = "2026-08-07T08:22:38.202Z" }, +] [[package]] name = "django-pg-baseline" From ccb9756fff68d1a31dc4f70329cd5d2b14000055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Fri, 7 Aug 2026 14:06:02 +0200 Subject: [PATCH 05/10] =?UTF-8?q?W=C5=82=C4=85cz=20FETCH=5FPEERS=20(Django?= =?UTF-8?q?=206.1)=20w=20adminie=20(#733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(admin): filtr "Wydział" na liście autorów listował 504 jednostki zamiast 7 wydziałów `AutorAdmin.list_filter` miał goły string "aktualna_jednostka__wydzial", z którego Django budowało `RelatedFieldListFilter`, a ten woła `field.get_choices()`. Po Fazie B (#438) denorm `Jednostka.wydzial` jest self-FK na `Jednostka`, więc `get_choices()` enumerowało CAŁĄ tabelę jednostek: na kopii bazy produkcyjnej 504 opcje w dropdownie zamiast 7 jednostek-korzeni ("wydziałów"), plus 504 zapytania na każdy request changelisty (każde `Jednostka.__str__` czyta `self.uczelnia`). Faza B naprawiła to `WydzialFilter`-em, ale tylko dla `JednostkaAdmin` — `AutorAdmin` został przeoczony. Dokładamy `WydzialAutoraFilter`: podklasę `WydzialFilter`, która dziedziczy bez zmian `lookups()` (tylko korzenie, zawężone do uczelni z requestu) i `has_output()` (bramka `uzywaj_wydzialow`), a nadpisuje jedynie `queryset()` — bo tu zawężamy `Autor`, więc do korzenia trzeba dojść przez `aktualna_jednostka__` (`Q(aktualna_jednostka__wydzial_id=v) | Q(aktualna_jednostka_id=v)`). Świadoma zmiana kontraktu URL: parametr filtra to teraz `?wydzial=` zamiast `?aktualna_jednostka__wydzial__id__exact=`. Filtry admina to ulotny stan UI, nie trwałe linki, więc to akceptujemy; stary querystring degraduje się do 400 (`DisallowedModelAdminLookup`, zmierzone testem), a nie do 500. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XWWemKMkQMQmHZZPSmCdid * fix(admin): listy filtrow admina jednym zapytaniem zamiast N+1 Dwa niezalezne defekty w `bpp/admin/filters.py`, oba wykryte pomiarem na kopii bazy produkcyjnej (504 jednostki, 68 tys. autorow): 1. `JednostkaFilter.lookups()` mial `select_related("wydzial")`, ale `Jednostka.__str__` czyta DWA FK -- takze `self.uczelnia` (bramka `uzywaj_wydzialow`). Kazda pozycja listy kosztowala osobny SELECT: 504 zapytania na KAZDE wejscie na changeliste autorow. Cacheops zamienial je na trafienia w Redis (`bpp.uczelnia` jest w regulach), wiec licznik zapytan SQL tego nie pokazywal -- ale 504 round-tripy do Redisa nadal kosztowaly ~200 ms na request. 2. `LogEntryFilterBase.lookups()` zawezal queryset przez `.only("pk", "username")`, a `BppUser.__str__` czyta jeszcze `last_name` i `first_name`. Kazde pole odroczone to osobny `refresh_from_db()` per uzytkownik, czyli DWA dodatkowe SELECT-y na wiersz -- "optymalizacja", ktora kosztowala zamiast oszczedzac. Na produkcji 156 zapytan na wejscie na changeliste wydawnictw ciaglych, i w przeciwienstwie do slownikow `bpp.bppuser` NIE jest cache'owany przez cacheops, wiec szly wprost do PostgreSQL. Zmierzony efekt (kopia produkcji, produkcyjne reguly CACHEOPS): changelist wyd. ciaglych 190 -> 34 zapytania, changelist autorow 620 -> 452 ms (przy tej samej liczbie zapytan SQL -- zniknely round-tripy do Redisa). Testy pilnuja sedna: obie listy powstaja DOKLADNIE jednym zapytaniem, niezaleznie od liczby pozycji. Drugi test dodatkowo asertuje, ze imie i nazwisko sa w etykiecie -- inaczej `only()` znow by je pominelo, a `__str__` po cichu degradowalby do samego `username`. Uporzadkowana tez kolejnosc importow w `test_filters.py` (plik nie przechodzil `ruff check` juz przed ta zmiana; pre-commit sprawdza tylko pliki zmieniane, wiec nikt tego nie zauwazyl). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XWWemKMkQMQmHZZPSmCdid * fix(browse): liczba autorow na indeksie jednostek jednym agregatem `browse/jednostki.html` wolal `item.aktualna_jednostka.count` -- relacja ODWROTNA (Autor -> Jednostka), wiec kazde uzycie to osobny `COUNT(*)`. Szablon uzywal go 3-4 razy na wiersz (warunek `> 0`, sama liczba, dwa warunki odmiany), a strona pokazuje domyslnie 150 jednostek. Na kopii bazy produkcyjnej dawalo to 514 zapytan na JEDNO wejscie na indeks jednostek -- najwiekszy pojedynczy koszt zapytan w calej czesci publicznej. Ani cacheops, ani fetch modes z Django 6.1 tego NIE lapaly: queryset dotyczy `bpp.autor`, ktorego nie ma w regulach CACHEOPS, a `FETCH_PEERS` obsluguje leniwe FK i pola odroczone -- nie `RelatedManager.count()`. Zmierzone: z globalnym FETCH_PEERS bylo 514 -> 514 zapytan, czyli zero zmiany. Lekarstwem jest adnotacja, nie tryb pobierania. Zmierzony efekt (kopia produkcji, produkcyjne reguly CACHEOPS): 514 -> 3 zapytania, 137 -> 20 ms. Adnotacja jest tu bezpieczna (nie zawyza liczb przez zdublowane wiersze), bo -- jak dokumentuje komentarz w `Browser.get_queryset` -- zadna sciezka filtrowania `JednostkiView` nie mnozy wierszy: filtr literki to `istartswith` na wlasnej kolumnie, fulltext dla `Jednostka` to predykat na jednokolumnowym tsvectorze bez JOIN-a, a `scope_jednostki_do_uczelni` porownuje skalarny FK. Sprawdzone tez wyczerpujaco: dla WSZYSTKICH 504 jednostek z bazy produkcyjnej adnotacja dala te same liczby, co `.count()` per wiersz (0 roznic, suma 60 711 autorow), a wyrenderowany HTML jest identyczny bajt w bajt. Dwa testy, bo zmiana ma dwa rozne ryzyka: * `test_JednostkiView_liczba_autorow_jednym_zapytaniem` -- sedno, czyli brak zapytania per wiersz, * `test_browse_jednostki_pokazuje_liczbe_autorow` -- literowka w nazwie adnotacji NIE wywalilaby wyjatku (Django renderuje nieistniejaca zmienna jako pusty lancuch), wiec strona po cichu przestalaby pokazywac liczby. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XWWemKMkQMQmHZZPSmCdid * feat(admin): wlacz FETCH_PEERS (Django 6.1) dla querysetow adminow Django 6.1 wprowadzilo *fetch modes*. `FETCH_PEERS` sprawia, ze PIERWSZE leniwe dotkniecie relacji (albo pola odroczonego) na obiekcie z querysetu dociaga ja HURTEM dla calego rodzenstwa z tego samego pobrania (`prefetch_related_objects` pod spodem) -- N+1 zamienia sie w 2 zapytania, bez zgadywania z gory, ktore FK dotknie szablon. Changelisty admina to najgestsze w BPP skupisko tego wzorca: `list_display` i `__str__` modeli siegaja po FK, ktorych nikt nie zadeklarowal w `list_select_related`. Ustawiamy tryb w `BaseBppAdminMixin.get_queryset`, czyli w jednym miejscu dla 31 adminow. Zmierzone na kopii bazy produkcyjnej (produkcyjne reguly CACHEOPS), zapytania i mediana czasu na request: changelist jednostek 66 -> 17 zapytan, 185 -> 120 ms changelist wyd. zwartych 220 -> 40 zapytan, 227 -> 184 ms changelist wyd. ciaglych 190 -> 38 zapytan, 221 -> 190 ms Dlaczego TU, a nie globalnie (podstawienie `DEFAULT_FETCH_MODE`): `track_peers` trzyma `weakref` do kazdej instancji z pobrania, wiec koszt ponosilby KAZDY queryset w aplikacji, a zysk jest skoncentrowany w adminie. Samo `get_queryset` wystarcza, bo `QuerySet._clone()` przenosi `_fetch_mode` -- tryb przezywa filtry, sortowanie i slicing dokladane przez dalsze mixiny i przez sam `ChangeList`. Test pilnuje obu tych wlasnosci osobno. Semantyka sie NIE zmienia: `fetch_one` i `fetch_many` ida ta sama sciezka managera (`_base_manager`) -- tryb nie zaczyna nagle odfiltrowywac rekordow, co jest istotne przy soft-delete. Sprawdzone tez empirycznie na danych produkcyjnych: 15 stron (publiczne, admin, API) zwrocilo wynik identyczny bajt w bajt w obu trybach. WYMAGA Django >= 6.1, dlatego ta gałąź celuje w `django-6.1`, a nie w `dev`. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XWWemKMkQMQmHZZPSmCdid --------- Co-authored-by: Claude Opus 5 (1M context) --- src/bpp/admin/autor.py | 3 +- src/bpp/admin/core.py | 36 +++++ src/bpp/admin/filters.py | 59 ++++++- .../autor-filtr-wydzial.bugfix.rst | 4 + .../fetch-peers-admin.feature.rst | 5 + .../wydajnosc-filtry-admina.bugfix.rst | 6 + .../wydajnosc-indeks-jednostek.bugfix.rst | 4 + src/bpp/templates/browse/jednostki.html | 4 +- .../test_admin/test_autor_wydzial_filter.py | 146 ++++++++++++++++++ src/bpp/tests/test_admin/test_fetch_peers.py | 61 ++++++++ src/bpp/tests/test_admin/test_filters.py | 72 ++++++++- .../test_browse/test_jednostka_jednostki.py | 59 +++++++ src/bpp/views/browse.py | 25 ++- 13 files changed, 474 insertions(+), 10 deletions(-) create mode 100644 src/bpp/newsfragments/autor-filtr-wydzial.bugfix.rst create mode 100644 src/bpp/newsfragments/fetch-peers-admin.feature.rst create mode 100644 src/bpp/newsfragments/wydajnosc-filtry-admina.bugfix.rst create mode 100644 src/bpp/newsfragments/wydajnosc-indeks-jednostek.bugfix.rst create mode 100644 src/bpp/tests/test_admin/test_autor_wydzial_filter.py create mode 100644 src/bpp/tests/test_admin/test_fetch_peers.py diff --git a/src/bpp/admin/autor.py b/src/bpp/admin/autor.py index 29aeb6f2f..4ead588d4 100644 --- a/src/bpp/admin/autor.py +++ b/src/bpp/admin/autor.py @@ -28,6 +28,7 @@ OrcidObecnyFilter, PBN_UID_IDObecnyFilter, PBNIDObecnyFilter, + WydzialAutoraFilter, ) from .helpers.fieldsets import ADNOTACJE_FIELDSET, ZapiszZAdnotacjaMixin from .helpers.site_filtered import SiteFilteredAdminMixin @@ -335,7 +336,7 @@ def has_delete_permission(self, request, obj=None): ] list_filter = [ JednostkaFilter, - "aktualna_jednostka__wydzial", + WydzialAutoraFilter, "tytul", PBNIDObecnyFilter, OrcidObecnyFilter, diff --git a/src/bpp/admin/core.py b/src/bpp/admin/core.py index 3ceef8e41..0e1fb363d 100644 --- a/src/bpp/admin/core.py +++ b/src/bpp/admin/core.py @@ -8,6 +8,7 @@ from django.conf import settings from django.contrib import admin from django.core.cache import cache +from django.db.models import FETCH_PEERS from django.db.models.fields import BLANK_CHOICE_DASH from django.forms import NullBooleanField from django.forms.widgets import HiddenInput @@ -117,6 +118,41 @@ class BaseBppAdminMixin(DynamicAdminFilterMixin): # ograniczenie wielkosci listy list_per_page = 50 + def get_queryset(self, request): + """Włącz ``FETCH_PEERS`` (Django 6.1) dla querysetów tego admina. + + Changelisty admina to najgęstsze w BPP skupisko N+1: ``list_display`` + i ``__str__`` modeli sięgają po FK, których nikt nie zadeklarował + w ``list_select_related``, a każde takie dotknięcie to osobny SELECT + per wiersz. ``FETCH_PEERS`` sprawia, że PIERWSZE leniwe dotknięcie + relacji (albo pola odroczonego) dociąga ją HURTEM dla całego + rodzeństwa z tego samego pobrania — N+1 zamienia się w 2 zapytania, + bez zgadywania z góry, które FK dotknie szablon. + + Zmierzone na kopii bazy produkcyjnej (z produkcyjnymi regułami + ``CACHEOPS``) — zapytania i mediana czasu na request: + + * changelist jednostek 66 → 17 zapytań, 185 → 120 ms + * changelist wyd. zwartych 220 → 40 zapytań, 227 → 184 ms + * changelist wyd. ciągłych 190 → 38 zapytań, 221 → 190 ms + + Dlaczego TU, a nie globalnie (podstawienie ``DEFAULT_FETCH_MODE``): + ``track_peers`` trzyma ``weakref`` do każdej instancji z pobrania, + więc koszt ponosiłby KAŻDY queryset w aplikacji, a zysk jest + skoncentrowany w adminie. Samo ``get_queryset`` wystarcza, bo + ``QuerySet._clone()`` przenosi ``_fetch_mode`` — tryb przeżywa + filtry, sortowanie i slicing dokładane przez dalsze mixiny + i przez sam ``ChangeList``. + + Semantyka się NIE zmienia: ``fetch_one`` i ``fetch_many`` idą tą samą + ścieżką managera (``_base_manager``), więc tryb nie zaczyna nagle + odfiltrowywać rekordów — istotne przy soft-delete. + + WYMAGA Django >= 6.1 (``QuerySet.fetch_mode``) — dlatego ta zmiana + celuje w gałąź ``django-6.1``, a nie w ``dev``. + """ + return super().get_queryset(request).fetch_mode(FETCH_PEERS) + def save_related(self, request, form, formsets, change): """ Przebuduj cache punktacji PO zapisaniu wszystkich inlines (autorzy/dyscypliny). diff --git a/src/bpp/admin/filters.py b/src/bpp/admin/filters.py index 058845a5b..4146e0b0c 100644 --- a/src/bpp/admin/filters.py +++ b/src/bpp/admin/filters.py @@ -326,6 +326,40 @@ def queryset(self, request, queryset): return queryset +class WydzialAutoraFilter(WydzialFilter): + """``WydzialFilter`` dla changelisty ``AutorAdmin`` (#438, domknięcie). + + Ten sam problem co w ``JednostkaAdmin``, tylko o jeden hop dalej: goły + ``list_filter = ("aktualna_jednostka__wydzial", ...)`` kazał Django + zbudować ``RelatedFieldListFilter``, a ten woła ``field.get_choices()``. + Skoro po Fazie B denorm ``Jednostka.wydzial`` jest self-FK na + ``Jednostka``, ``get_choices()`` enumerowało CAŁĄ tabelę jednostek -- + produkcyjnie 504 pozycje zamiast 7 wydziałów, plus 504 zapytania na + request (``Jednostka.__str__`` czyta ``self.uczelnia``). + + Z klasy bazowej dziedziczymy BEZ zmian ``lookups`` (tylko korzenie, + zawężone do uczelni z requestu) i ``has_output`` (bramka + ``uzywaj_wydzialow``) -- lista „wydziałów" jest ta sama niezależnie od + tego, co filtrujemy. Nadpisujemy wyłącznie ``queryset``, bo tu + zawężamy ``Autor``, a nie ``Jednostka``: do korzenia trzeba dojść przez + ``aktualna_jednostka__``. + """ + + def queryset(self, request, queryset): + v = self.value() + if v: + # Odpowiednik `Q(wydzial_id=v) | Q(pk=v)` z klasy bazowej, + # przetraversowany o jeden FK dalej: autorzy z jednostek + # niosących denorm ``wydzial=korzeń`` PLUS autorzy przypisani + # wprost do samego korzenia (korzeń nie wskazuje na siebie). + # Oba warunki to forward-FK (bez multi-valued join), więc unia + # nie duplikuje wierszy i nie potrzebuje ``distinct()``. + return queryset.filter( + Q(aktualna_jednostka__wydzial_id=v) | Q(aktualna_jednostka_id=v) + ) + return queryset + + class JednostkaFilter(SimpleListFilter): title = "Jednostka" parameter_name = "jednostka" @@ -337,8 +371,18 @@ def queryset(self, request, queryset): return queryset def lookups(self, request, model_admin): + # ``uczelnia`` w select_related, NIE tylko ``wydzial``: + # ``Jednostka.__str__`` czyta OBA FK — ``self.uczelnia`` (bramka + # ``uzywaj_wydzialow``) i ``self.wydzial`` (skrót w nawiasie). Bez + # ``uczelnia`` każda opcja listy kosztowała osobny SELECT: na bazie + # produkcyjnej 504 jednostki = 504 zapytania na KAŻDE wejście na + # changelistę autorów. Cacheops zamieniał je na trafienia w Redis + # (``bpp.uczelnia`` jest w regułach), więc licznik zapytań SQL tego + # nie pokazywał — ale 504 round-tripy do Redisa nadal kosztowały + # ~200 ms na request. return ( - (x.pk, str(x)) for x in Jednostka.objects.all().select_related("wydzial") + (x.pk, str(x)) + for x in Jednostka.objects.all().select_related("wydzial", "uczelnia") ) @@ -358,11 +402,22 @@ def logentries(self): ) def lookups(self, request, model_admin): + # ``only()`` MUSI wymieniać wszystkie pola, które czyta + # ``BppUser.__str__`` (``last_name``, ``first_name`` — patrz + # ``bpp/models/profile.py``), inaczej „optymalizacja" kosztuje + # zamiast oszczędzać: każde pole odroczone to osobny + # ``refresh_from_db()`` per użytkownik, czyli DWA dodatkowe SELECT-y + # na wiersz. Na bazie produkcyjnej to było 156 zapytań na wejście + # na changelistę wydawnictw ciągłych (78 użytkowników × 2 pola) — + # i w przeciwieństwie do słowników ``bpp.bppuser`` NIE jest + # cache'owany przez cacheops, więc szły wprost do PostgreSQL. + # + # Jeśli dokładasz pole do ``__str__``, dołóż je też tutaj. return ( (x.pk, str(x)) for x in BppUser.objects.filter( pk__in=self.logentries().values_list("user_id") - ).only("pk", "username") + ).only("pk", "username", "last_name", "first_name") ) diff --git a/src/bpp/newsfragments/autor-filtr-wydzial.bugfix.rst b/src/bpp/newsfragments/autor-filtr-wydzial.bugfix.rst new file mode 100644 index 000000000..aafdf2dbc --- /dev/null +++ b/src/bpp/newsfragments/autor-filtr-wydzial.bugfix.rst @@ -0,0 +1,4 @@ +Filtr „Wydział" na liście autorów w panelu admina listował wszystkie jednostki +(na produkcji 504 pozycje) zamiast samych wydziałów — teraz pokazuje wyłącznie +jednostki-korzenie i filtruje po całym poddrzewie wydziału, co dodatkowo +usuwa setki zbędnych zapytań przy każdym wyświetleniu listy. diff --git a/src/bpp/newsfragments/fetch-peers-admin.feature.rst b/src/bpp/newsfragments/fetch-peers-admin.feature.rst new file mode 100644 index 000000000..9c049106d --- /dev/null +++ b/src/bpp/newsfragments/fetch-peers-admin.feature.rst @@ -0,0 +1,5 @@ +Listy w panelu administracyjnym korzystają z nowego trybu pobierania relacji +z Django 6.1 (``FETCH_PEERS``): powiązane obiekty potrzebne do wyświetlenia +wiersza dociągane są hurtem dla całej strony, a nie osobno dla każdego +wiersza. Na dużej bazie skraca to czas otwarcia list o kilkanaście do +kilkudziesięciu procent, zależnie od listy. diff --git a/src/bpp/newsfragments/wydajnosc-filtry-admina.bugfix.rst b/src/bpp/newsfragments/wydajnosc-filtry-admina.bugfix.rst new file mode 100644 index 000000000..cb34fdfc4 --- /dev/null +++ b/src/bpp/newsfragments/wydajnosc-filtry-admina.bugfix.rst @@ -0,0 +1,6 @@ +Przyspieszono listy rozwijane filtrów w panelu administracyjnym. Filtr +„Jednostka" dociągał uczelnię osobnym zapytaniem dla każdej pozycji listy +(na dużej bazie ponad 500 zapytań na każde wejście na listę autorów), +a filtry „Utworzone przez" i „Ostatnio zmienione przez" pobierały imię +i nazwisko użytkownika dwoma dodatkowymi zapytaniami na pozycję. Teraz +każda z tych list powstaje jednym zapytaniem. diff --git a/src/bpp/newsfragments/wydajnosc-indeks-jednostek.bugfix.rst b/src/bpp/newsfragments/wydajnosc-indeks-jednostek.bugfix.rst new file mode 100644 index 000000000..0614d0939 --- /dev/null +++ b/src/bpp/newsfragments/wydajnosc-indeks-jednostek.bugfix.rst @@ -0,0 +1,4 @@ +Przyspieszono indeks jednostek w części publicznej. Liczba autorów przy +każdej jednostce była liczona osobnym zapytaniem, i to kilkukrotnie na +wiersz — na dużej bazie dawało to ponad 500 zapytań na jedno wyświetlenie +strony. Teraz liczby przychodzą jednym zapytaniem razem z listą jednostek. diff --git a/src/bpp/templates/browse/jednostki.html b/src/bpp/templates/browse/jednostki.html index fa0de4575..1ea415df4 100644 --- a/src/bpp/templates/browse/jednostki.html +++ b/src/bpp/templates/browse/jednostki.html @@ -125,10 +125,10 @@

{% endif %} - {% if item.aktualna_jednostka.count > 0 %} + {% if item.liczba_autorow > 0 %}
- {{ item.aktualna_jednostka.count }} {% if item.aktualna_jednostka.count == 1 %}autor{% elif item.aktualna_jednostka.count < 5 %}autorów{% else %}autorów{% endif %} + {{ item.liczba_autorow }} {% if item.liczba_autorow == 1 %}autor{% elif item.liczba_autorow < 5 %}autorów{% else %}autorów{% endif %}
{% endif %} diff --git a/src/bpp/tests/test_admin/test_autor_wydzial_filter.py b/src/bpp/tests/test_admin/test_autor_wydzial_filter.py new file mode 100644 index 000000000..1fa69c566 --- /dev/null +++ b/src/bpp/tests/test_admin/test_autor_wydzial_filter.py @@ -0,0 +1,146 @@ +"""Testy filtra „Wydział" na changeliście ``AutorAdmin`` (#438, domknięcie). + +Faza B (#438) zamieniła goły ``list_filter = ("wydzial", ...)`` na +``WydzialFilter`` w ``JednostkaAdmin``, ale ``AutorAdmin`` został z gołym +stringiem ``"aktualna_jednostka__wydzial"``. Ponieważ denorm +``Jednostka.wydzial`` jest self-FK na ``Jednostka``, ``RelatedFieldListFilter`` +enumerował CAŁĄ tabelę jednostek (produkcyjnie: 504 pozycje zamiast +7 wydziałów). +""" + +import pytest +from django.urls import reverse +from model_bakery import baker + +from bpp.admin.filters import WydzialAutoraFilter +from bpp.models import Autor, Jednostka, Uczelnia + + +def _spec_wydzial(response): + """Zwraca FilterSpec o tytule „Wydział" z wyrenderowanej changelisty.""" + for spec in response.context["cl"].filter_specs: + if str(spec.title) == "Wydział": + return spec + return None + + +@pytest.mark.django_db +def test_AutorAdmin_WydzialFilter_opcje_tylko_korzenie( + admin_client, + uczelnia: Uczelnia, + jednostka: Jednostka, + jednostka_podrzedna: Jednostka, + druga_jednostka: Jednostka, +): + # ISTOTA BUGA: opcji filtra ma być tyle, ile jednostek-korzeni („wydziałów"), + # a nie tyle, ile jednostek w bazie. Tu drzewo to 1 korzeń (fixture + # `wydzial`) + 3 węzły niżej — więc dokładnie 1 opcja, nie 4. + uczelnia.uzywaj_wydzialow = True + uczelnia.save() + + response = admin_client.get(reverse("admin:bpp_autor_changelist")) + assert response.status_code == 200 + + spec = _spec_wydzial(response) + assert spec is not None, "changelist nie renderuje filtra po wydziale" + + korzenie = Jednostka.objects.filter(parent__isnull=True, widoczna=True) + lookup_pks = {pk for pk, _ in spec.lookup_choices} + assert lookup_pks == {j.pk for j in korzenie} + assert len(lookup_pks) == korzenie.count() + # Węzły spod korzenia NIE mogą trafiać na listę wyboru „wydziału". + assert jednostka.pk not in lookup_pks + assert jednostka_podrzedna.pk not in lookup_pks + assert druga_jednostka.pk not in lookup_pks + + +@pytest.mark.django_db +def test_AutorAdmin_WydzialFilter_filtruje_poddrzewo( + admin_client, + jednostka: Jednostka, + jednostka_podrzedna: Jednostka, + druga_jednostka: Jednostka, +): + # Wybór korzenia zawęża do autorów z CAŁEGO poddrzewa: bezpośrednich + # dzieci, wnuków ORAZ przypisanych wprost do korzenia (ten ostatni + # przypadek obsługuje `| Q(aktualna_jednostka_id=v)`). + root = jednostka.parent + obcy_root = baker.make(Jednostka, parent=None, uczelnia=jednostka.uczelnia) + obca = baker.make(Jednostka, parent=obcy_root, uczelnia=jednostka.uczelnia) + + a_dziecko = baker.make(Autor, aktualna_jednostka=jednostka) + a_wnuk = baker.make(Autor, aktualna_jednostka=jednostka_podrzedna) + a_drugie_dziecko = baker.make(Autor, aktualna_jednostka=druga_jednostka) + a_korzen = baker.make(Autor, aktualna_jednostka=root) + a_obcy = baker.make(Autor, aktualna_jednostka=obca) + a_bez_jednostki = baker.make(Autor, aktualna_jednostka=None) + + response = admin_client.get( + reverse("admin:bpp_autor_changelist"), {"wydzial": root.pk} + ) + assert response.status_code == 200 + + result_list = list(response.context["cl"].result_list) + assert a_dziecko in result_list + assert a_wnuk in result_list + assert a_drugie_dziecko in result_list + assert a_korzen in result_list + assert a_obcy not in result_list + assert a_bez_jednostki not in result_list + + +@pytest.mark.django_db +def test_AutorAdmin_WydzialFilter_ukryty_gdy_uczelnia_bez_wydzialow( + admin_client, uczelnia: Uczelnia, jednostka: Jednostka +): + # Bramka `uzywaj_wydzialow` (dziedziczona z WydzialFilter): instytucja + # 1-progowa nie ma czego filtrować po wydziale. + uczelnia.uzywaj_wydzialow = False + uczelnia.save() + + response = admin_client.get(reverse("admin:bpp_autor_changelist")) + assert response.status_code == 200 + assert not any( + isinstance(spec, WydzialAutoraFilter) + for spec in response.context["cl"].filter_specs + ) + + +@pytest.mark.django_db +def test_AutorAdmin_WydzialFilter_widoczny_gdy_uczelnia_z_wydzialami( + admin_client, uczelnia: Uczelnia, jednostka: Jednostka +): + uczelnia.uzywaj_wydzialow = True + uczelnia.save() + + response = admin_client.get(reverse("admin:bpp_autor_changelist")) + assert response.status_code == 200 + assert any( + isinstance(spec, WydzialAutoraFilter) + for spec in response.context["cl"].filter_specs + ) + + +@pytest.mark.django_db +def test_AutorAdmin_stary_querystring_wydzialu_daje_400( + admin_client, jednostka: Jednostka +): + # Świadoma zmiana kontraktu URL: filtr zmienił parametr z + # `?aktualna_jednostka__wydzial__id__exact=` na `?wydzial=`. + # Filtry admina to ulotny stan UI (a nie trwałe linki), więc zerwanie + # starych URL-i akceptujemy — ale musi degradować się przewidywalnie, + # bez 500. + # + # ZMIERZONE zachowanie (nie założone): skoro pola nie ma już w + # `list_filter`, `ChangeList.get_filters` odrzuca lookup jako + # `DisallowedModelAdminLookup`. To podklasa `SuspiciousOperation`, więc + # handler wyjątków Django zamienia ją na **400 Bad Request** i loguje w + # kanale `django.security` — NIE jest to 500 ani redirect na `?e=1` + # (`?e=1` dostajemy tylko dla `IncorrectLookupParameters`, czyli dla + # DOZWOLONEGO pola z niepoprawną wartością). + root = jednostka.parent + response = admin_client.get( + reverse("admin:bpp_autor_changelist"), + {"aktualna_jednostka__wydzial__id__exact": root.pk}, + ) + assert response.status_code == 400 diff --git a/src/bpp/tests/test_admin/test_fetch_peers.py b/src/bpp/tests/test_admin/test_fetch_peers.py new file mode 100644 index 000000000..d5b6a1a45 --- /dev/null +++ b/src/bpp/tests/test_admin/test_fetch_peers.py @@ -0,0 +1,61 @@ +"""Kontrakt: adminy BPP pobierają dane w trybie ``FETCH_PEERS`` (Django 6.1). + +``BaseBppAdminMixin.get_queryset`` włącza ``FETCH_PEERS``, żeby pierwsze +leniwe dotknięcie relacji (albo pola odroczonego) dociągało ją hurtem dla +całego rodzeństwa z tego samego pobrania. To zamienia N+1 na changelistach +admina na 2 zapytania — bez zgadywania z góry, które FK dotknie szablon. + +Testy tutaj pilnują dwóch rzeczy, które łatwo zepsuć niechcący: + +1. tryb jest w ogóle ustawiany (ktoś mógłby nadpisać ``get_queryset`` + w podklasie i zapomnieć o ``super()``), +2. tryb PRZEŻYWA łańcuch ``.filter()/.order_by()/[slice]`` — to jest cała + przesłanka, dla której wystarcza jedno wywołanie w ``get_queryset``, + a nie łatanie każdego miejsca osobno. +""" + +import pytest +from django.contrib import admin as dj_admin +from django.db.models import FETCH_PEERS + +from bpp.admin.autor import AutorAdmin +from bpp.admin.jednostka import JednostkaAdmin +from bpp.admin.wydawnictwo_ciagle import Wydawnictwo_CiagleAdmin +from bpp.models import Autor, Jednostka, Wydawnictwo_Ciagle + + +def _queryset_admina(klasa_admina, model, rf, admin_user): + request = rf.get("/admin/") + request.user = admin_user + return klasa_admina(model, dj_admin.site).get_queryset(request) + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "klasa_admina,model", + [ + (AutorAdmin, Autor), + (JednostkaAdmin, Jednostka), + (Wydawnictwo_CiagleAdmin, Wydawnictwo_Ciagle), + ], +) +def test_admin_pobiera_w_trybie_fetch_peers(klasa_admina, model, rf, admin_user): + queryset = _queryset_admina(klasa_admina, model, rf, admin_user) + assert queryset._fetch_mode is FETCH_PEERS, klasa_admina.__name__ + + +@pytest.mark.django_db +def test_fetch_peers_przezywa_lancuch_querysetu(rf, admin_user): + """Tryb przenosi się przez ``_clone()`` — filtr, sortowanie i slicing. + + ``ChangeList`` i dalsze mixiny dokładają do querysetu własne ``filter``, + ``order_by`` i wycinek strony. Gdyby tryb ginął przy klonowaniu, + ustawienie go w ``get_queryset`` nic by nie dawało. + """ + queryset = _queryset_admina(AutorAdmin, Autor, rf, admin_user) + + assert queryset.filter(pokazuj=True)._fetch_mode is FETCH_PEERS + assert queryset.order_by("nazwisko")._fetch_mode is FETCH_PEERS + assert queryset.filter(pokazuj=True).order_by("nazwisko")[:10]._fetch_mode is ( + FETCH_PEERS + ) diff --git a/src/bpp/tests/test_admin/test_filters.py b/src/bpp/tests/test_admin/test_filters.py index 7a4813c21..1e4c16167 100644 --- a/src/bpp/tests/test_admin/test_filters.py +++ b/src/bpp/tests/test_admin/test_filters.py @@ -1,11 +1,14 @@ import pytest -from model_bakery import baker - from django.contrib.admin.models import ADDITION, CHANGE, LogEntry from django.contrib.contenttypes.models import ContentType +from model_bakery import baker -from bpp.admin.filters import OstatnioZmienionePrzezFilter, UtworzonePrzezFilter -from bpp.models import Wydawnictwo_Zwarte +from bpp.admin.filters import ( + JednostkaFilter, + OstatnioZmienionePrzezFilter, + UtworzonePrzezFilter, +) +from bpp.models import Autor, BppUser, Wydawnictwo_Zwarte @pytest.mark.django_db @@ -57,3 +60,64 @@ def test_UtworzonePrzeZFilter(wydawnictwo_zwarte, admin_user, normal_django_user user_ids = [x[0] for x in f.lookups(None, None)] assert admin_user.pk in user_ids + + +@pytest.mark.django_db +def test_JednostkaFilter_lookups_jednym_zapytaniem(uczelnia, django_assert_num_queries): + """Opcje filtra „Jednostka" powstają JEDNYM zapytaniem. + + Regresja: ``lookups()`` woła ``str(x)`` na każdej jednostce, a + ``Jednostka.__str__`` czyta DWA FK — ``self.uczelnia`` (bramka + ``uzywaj_wydzialow``) i ``self.wydzial`` (skrót w nawiasie). + ``select_related`` pokrywał tylko ``wydzial``, więc każda opcja listy + kosztowała osobny SELECT po uczelnię: na bazie produkcyjnej 504 jednostki + = 504 zapytania na KAŻDE wejście na changelistę autorów. + """ + from bpp.tests.util import any_jednostka + + for numer in range(4): + any_jednostka(nazwa=f"Jednostka Filtrowa {numer}", uczelnia=uczelnia) + + filtr = JednostkaFilter(None, {}, Autor, None) + + with django_assert_num_queries(1): + etykiety = [etykieta for _pk, etykieta in filtr.lookups(None, None)] + + assert len([e for e in etykiety if "Jednostka Filtrowa" in e]) == 4 + + +@pytest.mark.django_db +def test_UtworzonePrzezFilter_lookups_jednym_zapytaniem( + wydawnictwo_zwarte, django_assert_num_queries +): + """Opcje filtra „Utworzone przez" powstają JEDNYM zapytaniem. + + Regresja: ``lookups()`` zawężał queryset przez ``.only("pk", "username")``, + ale ``BppUser.__str__`` czyta też ``last_name`` i ``first_name``. Każde + pole odroczone to osobny ``refresh_from_db()`` per użytkownik, czyli DWA + dodatkowe SELECT-y na wiersz — „optymalizacja", która kosztowała zamiast + oszczędzać (na produkcji 156 zapytań na wejście na changelistę wydawnictw + ciągłych). Test pilnuje, żeby ``only()`` nadążało za ``__str__``. + """ + content_type_id = ContentType.objects.get_for_model(wydawnictwo_zwarte).pk + + for numer in range(3): + LogEntry.objects.create( + action_flag=ADDITION, + object_id=wydawnictwo_zwarte.pk, + user=baker.make( + BppUser, first_name=f"Imie{numer}", last_name=f"Nazwisko{numer}" + ), + content_type_id=content_type_id, + ) + + filtr = UtworzonePrzezFilter(None, {}, Wydawnictwo_Zwarte, None) + + with django_assert_num_queries(1): + etykiety = [etykieta for _pk, etykieta in filtr.lookups(None, None)] + + assert len(etykiety) == 3 + # Nazwisko i imię MUSZĄ się pojawić — inaczej ``only()`` znów je pominęło, + # a ``__str__`` po cichu degradowałby do samego ``username``. + assert all("Nazwisko" in etykieta for etykieta in etykiety), etykiety + assert all("Imie" in etykieta for etykieta in etykiety), etykiety diff --git a/src/bpp/tests/test_views/test_browse/test_jednostka_jednostki.py b/src/bpp/tests/test_views/test_browse/test_jednostka_jednostki.py index 70f2c692a..0195db59f 100644 --- a/src/bpp/tests/test_views/test_browse/test_jednostka_jednostki.py +++ b/src/bpp/tests/test_views/test_browse/test_jednostka_jednostki.py @@ -349,3 +349,62 @@ def test_jednostka_aktualni_pracownicy( html = normalize_html(res.rendered_content) assert "Obecni pracownicy" in html assert "Byli pracownicy" in html + + +def test_JednostkiView_liczba_autorow_jednym_zapytaniem( + uczelnia, rf, django_assert_num_queries +): + """Liczba autorów per jednostka przychodzi z adnotacji, nie z pętli. + + Regresja: szablon ``browse/jednostki.html`` wołał + ``item.aktualna_jednostka.count`` — relacja ODWROTNA (Autor → Jednostka), + więc każde użycie to osobny ``COUNT(*)``, a szablon używał go 3–4 razy na + wiersz. Na bazie produkcyjnej (150 jednostek na stronę) dawało to ~514 + zapytań na jedno wejście na indeks jednostek. + + Test pilnuje SEDNA: przejście po całym querysecie i odczytanie + ``liczba_autorow`` dla każdego wiersza to JEDNO zapytanie, niezależnie od + liczby jednostek. Sam queryset budujemy poza pomiarem, bo + ``Uczelnia.objects.get_for_request`` też odpytuje bazę, a nie o nim tu mowa. + """ + from bpp.tests.util import any_jednostka + + z_autorami = any_jednostka(nazwa="Jednostka Z Autorami", uczelnia=uczelnia) + bez_autorow = any_jednostka(nazwa="Jednostka Bez Autorow", uczelnia=uczelnia) + for _ in range(3): + baker.make(Autor, aktualna_jednostka=z_autorami) + + widok = JednostkiView() + widok.request = rf.get(reverse("bpp:browse_jednostki")) + widok.kwargs = {} + queryset = widok.get_queryset() + + with django_assert_num_queries(1): + liczby = {j.pk: j.liczba_autorow for j in queryset} + + assert liczby[z_autorami.pk] == 3 + assert liczby[bez_autorow.pk] == 0 + + +def test_browse_jednostki_pokazuje_liczbe_autorow(uczelnia, client): + """Liczba autorów jest widoczna na stronie — nie zniknęła z szablonu. + + Odrębny test od powyższego, bo optymalizacja przeniosła źródło liczby + z ``item.aktualna_jednostka.count`` na adnotację ``item.liczba_autorow``. + Literówka w nazwie adnotacji NIE wywaliłaby żadnego wyjątku — Django + renderuje nieistniejącą zmienną jako pusty łańcuch — więc strona po cichu + przestałaby pokazywać liczby. Ten test to wyłapuje. + """ + from bpp.tests.util import any_jednostka + + jednostka_z_autorami = any_jednostka( + nazwa="Jednostka Licznikowa", uczelnia=uczelnia + ) + for _ in range(2): + baker.make(Autor, aktualna_jednostka=jednostka_z_autorami) + + res = client.get(reverse("bpp:browse_jednostki")) + tresc = normalize_html(res.rendered_content) + + assert "Jednostka Licznikowa" in tresc + assert "2 autorów" in tresc diff --git a/src/bpp/views/browse.py b/src/bpp/views/browse.py index dd7db3377..27d9fed07 100644 --- a/src/bpp/views/browse.py +++ b/src/bpp/views/browse.py @@ -735,7 +735,30 @@ def get_queryset(self): if uczelnia.pokazuj_tylko_jednostki_nadrzedne: qry = qry.filter(parent=None) - ret = qry.only("nazwa", "slug", "wydzial").select_related("wydzial") + # ``liczba_autorow`` liczona JEDNYM agregatem, a nie per wiersz w + # szablonie. Wcześniej `browse/jednostki.html` wołał + # ``item.aktualna_jednostka.count`` — relacja ODWROTNA (Autor → + # Jednostka), więc każde użycie to osobny ``COUNT(*)``, a szablon + # używał go 3–4 razy na wiersz. Przy 150 jednostkach na stronę dawało + # to ~514 zapytań na jedno wejście na indeks jednostek (zmierzone na + # bazie produkcyjnej: 514 → 3 zapytania, 137 ms → 20 ms). + # + # Ani cacheops, ani fetch modes z Django 6.1 tego NIE łapały: + # queryset dotyczy ``bpp.autor``, którego nie ma w regułach + # ``CACHEOPS``, a ``FETCH_PEERS`` obsługuje leniwe FK i pola + # odroczone — nie ``RelatedManager.count()``. + # + # Adnotacja jest tu bezpieczna (nie zawyża liczb przez zdublowane + # wiersze), bo — jak dokumentuje komentarz w ``Browser.get_queryset`` + # — żadna ścieżka filtrowania ``JednostkiView`` nie mnoży wierszy: + # filtr literki to ``istartswith`` na własnej kolumnie, fulltext dla + # ``Jednostka`` to predykat na jednokolumnowym tsvectorze bez JOIN-a, + # a ``scope_jednostki_do_uczelni`` porównuje skalarny FK. + ret = ( + qry.only("nazwa", "slug", "wydzial") + .select_related("wydzial") + .annotate(liczba_autorow=Count("aktualna_jednostka")) + ) if ordering: ret = ret.order_by(*ordering) From 02a840a8ac2259ff009d76bb23abd536ae723901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Fri, 7 Aug 2026 14:12:17 +0200 Subject: [PATCH 06/10] docs(bench): harness pomiarowy ORM-a + handoff niedokonczonego pomiaru Audyt wydajnosci pod Django 6.1 zostal zrobiony na kopii bazy produkcyjnej przy pomocy harnessu, ktory dotad zyl jako nietrackowane pliki na jednej maszynie. To czynilo wyniki nieodtwarzalnymi, a wnioski niesprawdzalnymi -- wiec harness wchodzi do repo razem z dokumentacja. `bench/` (4 skrypty): * `bench_orm.py` -- `--inwentarz` (detekcja N+1 przez wlasny FetchMode, ktory liczy leniwe pobrania ze stosem wywolan) oraz `--pomiar [--tryb peers]`, * `bench_rownowaznosc.py` -- dowod, ze FETCH_PEERS nie zmienia tego, co strona zwraca (porownanie bajt w bajt, po wyzerowaniu tokenow CSRF i znacznika czasu), * `bench_atrybucja.py` -- rozbicie pobran JEDNEGO pola na miejsca wywolania (tym rozwiazalem rozbieznosc 1058 pobran przy 504 jednostkach: dwa rozne filtry admina), * `bench_licznik_wywolan.py` -- licznik budow ChangeList i konsumpcji generatora filtra (obalil moja hipoteze o podwojnym ChangeList -- memoizacja dziala). `src/django_bpp/settings/bench.py` + `bench_prod.py`: DWA warianty konfiguracji, i to rozroznienie jest sedno rzetelnosci pomiaru. `local.py` NIE definiuje `CACHEOPS` wcale, a produkcja cache'uje `bpp.uczelnia`, `bpp.jednostka`, `bpp.tytul` i pozostale slowniki -- czyli dokladnie te modele, w ktore uderzaja znalezione N+1. Pomiar tylko na `local.py` ZAWYZA zysk: changelist autorow pokazywal 1102 -> 96 zapytan bez cacheops, a z produkcyjnymi regulami 27 -> 27, czyli zysk ZEROWY. `bench_prod.py` czyta reguly z `production.py` przez AST (bez wykonywania modulu), zeby nie mierzyc wlasnej, rozjezdzajacej sie kopii. `docs/deweloper/benchmark-orm.md` opisuje procedure, oba warianty, pulapki (dump jest w formacie katalogowym `pg_dump -Fd`, wiec `run-site --from-dump` go nie przyjmie; cacheops pikluje instancje, wiec przy zmianie wersji Django trzeba FLUSHDB) i -- najwazniejsze -- jak NIE dac sie oszukac czasom: scenariusz kontrolny `admin: zrodlo (changelist)` nie jest tkniety zadna optymalizacja i ma stale 16 zapytan, wiec jego rozrzut miedzy przebiegami to zmierzona podloga szumu hosta. `HANDOFF-pomiar-wydajnosci-2026-08-07.md`: liczby zapytan sa zmierzone i pewne (powtorzyly sie w pieciu przebiegach), ale CZASOW nie udalo sie zmierzyc rzetelnie -- host jest stale obciazony rownoleglymi sesjami i suitami testowymi. Kontroler pokazal skok 320 -> 420 ms na NIEZMIENIONEJ sciezce kodu, co dowodzi, ze roznice czasowe pochodza z hosta, a nie z kodu. Handoff opisuje, co zostalo do zrobienia, jakie jest kryterium sukcesu i siedem pulapek, w ktore juz wpadlem. Nic z tego nie wplywa na kod produkcyjny: `bench/` jest poza `testpaths = src`, a moduly settings dziedzicza po `local.py`, ktory w produkcji sie nie laduje. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XWWemKMkQMQmHZZPSmCdid --- HANDOFF-pomiar-wydajnosci-2026-08-07.md | 208 ++++++++++ bench/bench_atrybucja.py | 93 +++++ bench/bench_licznik_wywolan.py | 91 +++++ bench/bench_orm.py | 398 ++++++++++++++++++++ bench/bench_rownowaznosc.py | 110 ++++++ docs/deweloper/benchmark-orm.md | 135 +++++++ src/bpp/newsfragments/benchmark-orm.doc.rst | 5 + src/django_bpp/settings/bench.py | 53 +++ src/django_bpp/settings/bench_prod.py | 87 +++++ 9 files changed, 1180 insertions(+) create mode 100644 HANDOFF-pomiar-wydajnosci-2026-08-07.md create mode 100644 bench/bench_atrybucja.py create mode 100644 bench/bench_licznik_wywolan.py create mode 100644 bench/bench_orm.py create mode 100644 bench/bench_rownowaznosc.py create mode 100644 docs/deweloper/benchmark-orm.md create mode 100644 src/bpp/newsfragments/benchmark-orm.doc.rst create mode 100644 src/django_bpp/settings/bench.py create mode 100644 src/django_bpp/settings/bench_prod.py diff --git a/HANDOFF-pomiar-wydajnosci-2026-08-07.md b/HANDOFF-pomiar-wydajnosci-2026-08-07.md new file mode 100644 index 000000000..cc0d4ff0c --- /dev/null +++ b/HANDOFF-pomiar-wydajnosci-2026-08-07.md @@ -0,0 +1,208 @@ +# HANDOFF: dokończyć pomiar czasów wydajności (drugi komputer) + +**Data:** 2026-08-07 +**Gałąź:** `django-6.1` +**Powód handoffu:** host `mac-mini` jest stale obłożony (równolegle biegną +inne sesje i suity testowe), więc **nie da się na nim zmierzyć czasów +odpowiedzi**. Liczby zapytań są już zmierzone i pewne — brakuje wyłącznie +wiarygodnych czasów ściany. + +--- + +## Co jest ZROBIONE (nie powtarzaj) + +Audyt Django 6.1 pod kątem wydajności + cztery naprawione defekty. +Pełny raport: `AUDYT-DJANGO-6.1-WYDAJNOSC.md` (nietrackowany, w korzeniu +worktree `bpp-django-6.1`). + +### Ustalenia, które są już pewne + +1. **Sam upgrade do Django 6.1 nie przyspiesza niczego.** Liczby zapytań + bit w bit identyczne jak na 5.2 we wszystkich 15 scenariuszach, w obu + wariantach konfiguracji cache. Zero regresji — i zero zysku. +2. Zysk wymaga jawnego opt-inu w *fetch modes* + (`QuerySet.fetch_mode(FETCH_PEERS)`). +3. Najcenniejsze w 6.1 jest `FETCH_RAISE`/własny `FetchMode` jako + **detektor N+1**. Tym mechanizmem znalazłem cztery defekty, których nie + widać w kodzie. + +### Wdrożone poprawki + +Na `dev` (Django 5.2, wypchnięte): + +| commit | co | +|---|---| +| `f509c7bdc` | filtr „Wydział" na `/admin/bpp/autor/` listował **504 jednostki zamiast 7** (bug UI, nie tylko koszt) | +| `5ffc83f50` | `JednostkaFilter` bez `select_related("uczelnia")`; `LogEntryFilterBase.only()` bez `last_name`/`first_name` | +| `6416e2ac0` | indeks jednostek: `.count()` na relacji odwrotnej 3–4× na wiersz → `annotate()` | + +Na `django-6.1`: + +| commit | co | +|---|---| +| `ccb9756ff` | PR #733 (squash): `FETCH_PEERS` w `BaseBppAdminMixin` — **wymaga 6.1** | + +### Zmierzone liczby zapytań (pewne, powtórzone w 5 przebiegach) + +Konfiguracja z produkcyjnymi regułami `CACHEOPS`: + +``` +scenariusz przed po +publiczne: lista jednostek 514 3 +admin: wyd. ciągłe (changelist) 190 34 +admin: wyd. zwarte (changelist) 220 36 +admin: jednostka (changelist) 66 17 +admin: autor (changelist) 27 27 (spadły round-tripy do Redisa, nie SQL) +admin: źródło (changelist) 16 16 ← KONTROLER, nietknięty +pozostałe 9 scenariuszy bez zmian +``` + +### Testy + +* `django-6.1` (po mergu #733): `1016 passed, 1 skipped` +* `dev` (Django 5.2): `1012 passed, 1 skipped` + (różnica = 4 testy `test_fetch_peers.py`, które żyją tylko na 6.1) + +Zakres: `src/bpp/tests/test_admin/` + `src/bpp/tests/test_views/`. + +--- + +## Co ZOSTAŁO do zrobienia + +**Jedno zadanie: zmierzyć czasy odpowiedzi na cichym hoście.** + +Dotychczasowe pomiary czasu są niewiarygodne i **nie wolno ich cytować jako +wyniku**. Dowód: scenariusz kontrolny `admin: źródło (changelist)` ma te +same 16 zapytań przed i po wszystkich zmianach (żadna go nie dotyka), a +zmierzony czas skakał **320 → 420 ms**. Skoro niezmieniona ścieżka kodu daje ++100 ms, to różnice czasowe pochodzą z obciążenia hosta, nie z kodu. Prawie +każdy pomiar dostawał flagę `SZUM` (odchylenie > 10% mediany). + +Liczby czasowe, które trafiły do wiadomości commitów i do opisu PR #733 +(np. „185 → 120 ms"), pochodzą ze spokojniejszego okna i są **rzędem +wielkości, nie pomiarem** — tak też je tam opisałem. Po czystym pomiarze +warto je zastąpić albo potwierdzić. + +### Kryterium sukcesu + +Dla każdego scenariusza podać medianę i odchylenie z >= 25 powtórzeń, przy +czym: + +* **kontroler `admin: źródło (changelist)` musi wypaść stabilnie** (rozrzut + < 10% mediany, brak flagi `SZUM`) — inaczej cały przebieg jest do + wyrzucenia, +* różnicę uznajemy za realną tylko wtedy, gdy jest **większa niż rozrzut + kontrolera**. + +--- + +## Warunki wstępne na drugim komputerze + +1. **Docker** (testcontainers + kontenery benchmarkowe). +2. **Cichy host** — żadnych równoległych suit testowych, `run-site` ani + innych sesji agenta. To jest cały powód tego handoffu. +3. **Dump bazy produkcyjnej.** To jedyna rzecz, której NIE ma w repo: + `db-backup-20260603-023000.tar.gz`, ~772 MB, leży na + `/Volumes/SSD/` na `mac-mini` (symlink z `~`). Trzeba go przekopiować, + np. przez udział SMB `mpasternak` albo `rsync`. Bez niego nie ma pomiaru + — świeża baza nie pokaże N+1 (ma pojedyncze wiersze zamiast 504 + jednostek i 68 tys. autorów). + Alternatywa: mniejszy `db-backup-20260428-093811.pg_dump` (~48 MB), + ale ma inny wolumen danych, więc liczby nie będą porównywalne z tymi + wyżej. + +--- + +## Procedura + +Cała mechanika jest opisana w `docs/deweloper/benchmark-orm.md` — poniżej +skrót właściwy dla tego zadania. + +```bash +git clone git@github.com:iplweb/bpp.git && cd bpp +git checkout django-6.1 +uv sync + +# 1. Kontenery (nietypowe porty, żeby nie kolidować z niczym) +docker run -d --name bpp-bench-pg -p 55432:5432 \ + -e POSTGRES_USER=bpp -e POSTGRES_PASSWORD=password -e POSTGRES_DB=bpp \ + --shm-size=1g iplweb/bpp_dbserver:psql-16.13 \ + -c shared_buffers=1GB -c work_mem=64MB -c maintenance_work_mem=512MB +docker run -d --name bpp-bench-redis -p 55379:6379 redis:7-alpine + +# 2. Baza (dump to format KATALOGOWY pg_dump -Fd w tarze, nie .sql!) +gzip -dc db-backup-20260603-023000.tar.gz | tar -xf - +PGPASSWORD=password pg_restore -h localhost -p 55432 -U bpp -d bpp \ + --no-owner --no-privileges --no-comments -j 6 db-backup-20260603-023000/ +DJANGO_SETTINGS_MODULE=django_bpp.settings.bench \ + uv run python src/manage.py migrate --noinput + +# 3. Pomiar PO zmianach (czubek django-6.1) +docker exec bpp-bench-redis redis-cli -n 7 FLUSHDB +DJANGO_SETTINGS_MODULE=django_bpp.settings.bench_prod \ + uv run python bench/bench_orm.py --pomiar --powtorzenia 25 > /tmp/po.txt + +# 4. Pomiar PRZED zmianami — commit-rodzic squasha #733. +# Harness trzeba przenieść, bo w ee5e81a35 jeszcze nie istniał: +git checkout ee5e81a35 +git checkout django-6.1 -- bench/ src/django_bpp/settings/bench.py \ + src/django_bpp/settings/bench_prod.py +docker exec bpp-bench-redis redis-cli -n 7 FLUSHDB +DJANGO_SETTINGS_MODULE=django_bpp.settings.bench_prod \ + uv run python bench/bench_orm.py --pomiar --powtorzenia 25 > /tmp/przed.txt +git checkout . && git checkout django-6.1 +``` + +**UWAGA na `ee5e81a35`:** to stan przed CAŁĄ pracą wydajnościową na linii +6.1, czyli bez `FETCH_PEERS` — ale też bez poprawek z `dev` (A/C/D/B), +bo #733 wszedł jako squash i wciągnął je razem ze sobą. Czyli para +`ee5e81a35` vs `ccb9756ff` mierzy **efekt łączny wszystkich czterech +poprawek + FETCH_PEERS**, co jest dokładnie tym, co chcemy pokazać. + +Jeśli chcesz rozbić wkład `FETCH_PEERS` osobno od poprawek A/C/D/B, użyj +`--tryb peers` na czubku `dev` (Django 5.2 nie ma fetch modes, więc to +trzeba zrobić na 6.1 — zob. „Ograniczenia" w dokumentacji harnessu). + +### Czyszczenie po pomiarze + +```bash +docker rm -f bpp-bench-pg bpp-bench-redis +``` + +--- + +## Pułapki, w które już wpadłem (nie powtarzaj) + +1. **`grep -c` bez mianownika kłamie.** `grep -c "^\[ \]"` zwrócił `0` + („brak zaległych migracji"), a komenda po prostu **wywaliła się + tracebackiem** i nie wypisała nic. Zawsze licz też mianownik. +2. **`cmd > log 2>&1; echo "exit=$?"` w potoku mierzy exit ostatniego + członu.** Dwa razy uwierzyłem w „exit 0", gdy `migrate` padał. + Zapisuj kod wyjścia JAWNIE do pliku z logiem. +3. **`pytest ... | tail -25` gubi podsumowanie**, bo logi zamykanych + kontenerów testcontainers wychodzą PO nim. Zapisuj pełny output do + pliku i grepuj. +4. **Cacheops pikluje instancje modeli.** Pomiar 6.1 czytający wpisy + zapisane przez 5.2 daje `RuntimeWarning` i nieważne wyniki. `FLUSHDB` + przy każdej zmianie wersji. +5. **`constance` odrzuca LocMemCache** (`ImproperlyConfigured`) — dlatego + `bench.py` trzyma `constance_cache` na Redisie. +6. **Nie wyrzucaj `easyaudit` z `INSTALLED_APPS`** — `bpp.0470` deklaruje + zależność migracji od `easyaudit.0001_initial`, więc graf migracji się + nie zbuduje. Wystarczy zgasić middleware i hooki sygnałów (tak robi + `bench.py`). +7. **Nie ubijaj kontenerów wzorcem na współdzielonym hoście.** Przy + sprzątaniu dwa razy trafiły mi na listę „sierot" CUDZE, żywe przebiegi + pytest — bo w trakcie detekcji startował kolejny Ryuk. Selekcjonuj po + nazwie/ID i wypisz listę PRZED usunięciem. + +--- + +## Stan repozytorium + +* `dev` — wypchnięte, `6416e2ac0` +* `django-6.1` — wypchnięte, `ccb9756ff` (+ ten handoff i `bench/`) +* PR #733 — **zmergowany** +* PR #731 (Django 6.1 → dev) — otwarty +* `AUDYT-DJANGO-6.1-WYDAJNOSC.md` — nietrackowany, tylko na `mac-mini`; + jeśli potrzebny na drugiej maszynie, skopiuj razem z dumpem diff --git a/bench/bench_atrybucja.py b/bench/bench_atrybucja.py new file mode 100644 index 000000000..4bc56837c --- /dev/null +++ b/bench/bench_atrybucja.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +"""Rozbij leniwe pobrania JEDNEGO pola na konkretne miejsca w kodzie. + +``bench_orm.py --inwentarz`` pokazuje pierwsze miejsce dla danej pary +(model, pole). Gdy licznik nie zgadza się z liczbą wierszy na stronie +(np. 1058 pobrań ``Jednostka.uczelnia`` przy 504 jednostkach w bazie), +trzeba wiedzieć, ILE pobrań przychodzi z KTÓREJ ścieżki wywołania — bo +inaczej opowiadamy o mechanizmie, którego nie sprawdziliśmy. + +Ten skrypt liczy pobrania per *podpis stosu* zredukowany do ramek kodu +projektu — czyli „kto to wywołał", a nie tylko „gdzie pękło". + +Użycie:: + + uv run python bench_atrybucja.py [Model.pole] [etykieta scenariusza] + +Domyślnie ``Jednostka.uczelnia`` na ``admin: autor (changelist)``. Działa +zarówno dla relacji (``fetcher`` = deskryptor FK), jak i dla pól +odroczonych (``fetcher`` = ``DeferredAttribute``) — oba mają ``.field``. +""" + +from __future__ import annotations + +import collections +import os +import sys +import traceback + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_bpp.settings.bench") + +import django # noqa: E402 + +django.setup() + +from bench_orm import ( # noqa: E402 + KATALOG_PROJEKTU, + odkryj_scenariusze, + ustaw_tryb_globalnie, +) +from django.db.models.fetch_modes import FetchMode # noqa: E402 + +CEL = tuple((sys.argv[1] if len(sys.argv) > 1 else "Jednostka.uczelnia").split(".")) +SCENARIUSZ = sys.argv[2] if len(sys.argv) > 2 else "admin: autor (changelist)" + + +def podpis_stosu(pomin=3, ile_ramek=4): + """Do ``ile_ramek`` najgłębszych ramek kodu projektu, od najgłębszej.""" + ramki = [] + for ramka, linia in traceback.walk_stack(sys._getframe(pomin)): + plik = ramka.f_code.co_filename + if plik.startswith(KATALOG_PROJEKTU): + ramki.append( + f"{os.path.relpath(plik, KATALOG_PROJEKTU)}:{linia}" + f" ({ramka.f_code.co_name})" + ) + if len(ramki) >= ile_ramek: + break + return " ← ".join(ramki) or "(brak ramek projektu)" + + +class FetchAtrybucja(FetchMode): + track_peers = False + + def __init__(self): + self.per_stos = collections.Counter() + self.suma = 0 + + def fetch(self, fetcher, instance): + if (type(instance).__name__, fetcher.field.name) == CEL: + self.suma += 1 + self.per_stos[podpis_stosu()] += 1 + fetcher.fetch_one(instance) + + +def main(): + szpieg = FetchAtrybucja() + ustaw_tryb_globalnie(szpieg) + + for etykieta, fn in odkryj_scenariusze(): + if etykieta != SCENARIUSZ: + continue + odp = fn() + print(f"\n# {SCENARIUSZ} (HTTP {odp.status_code})") + print(f"# leniwych pobrań {CEL[0]}.{CEL[1]}: {szpieg.suma}\n") + for stos, ile in szpieg.per_stos.most_common(10): + print(f"{ile:>6}× {stos}") + return 0 + print(f"Nie znalazłem scenariusza {SCENARIUSZ!r}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bench/bench_licznik_wywolan.py b/bench/bench_licznik_wywolan.py new file mode 100644 index 000000000..bfaaea8a8 --- /dev/null +++ b/bench/bench_licznik_wywolan.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +"""Policz, ile RAZY na jeden request admina powstaje ChangeList i lookupy filtra. + +Atrybucja (``bench_atrybucja.py``) pokazała 504 + 504 pobrań +``Jednostka.uczelnia``, czyli enumerację wszystkich jednostek DWA razy na +request — mimo istnienia ``PerRequestChangelistInstanceMixin``, który ma +memoizować ``ChangeList`` w obrębie żądania. + +Zamiast wnioskować ze stosów, liczymy wprost: + +* ile razy woła się ``JednostkaFilter.lookups`` i ile razy jego generator + faktycznie zostaje skonsumowany (to dwie różne rzeczy — ``lookups`` + zwraca generator expression, więc samo wywołanie nic nie odpytuje), +* ile razy ``get_changelist_instance`` trafia w cache, a ile razy pudłuje. +""" + +from __future__ import annotations + +import os +import sys + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_bpp.settings.bench") + +import django # noqa: E402 + +django.setup() + +from bench_orm import odkryj_scenariusze # noqa: E402 + +LICZNIKI = { + "lookups_wywolan": 0, + "lookups_skonsumowanych": 0, + "cl_pudel": 0, + "cl_trafien": 0, +} + + +def zainstaluj_liczniki(): + from bpp.admin.autor import AutorAdmin + from bpp.admin.filters import JednostkaFilter + from bpp.admin.xlsx_export.mixins import PerRequestChangelistInstanceMixin + + oryginalne_lookups = JednostkaFilter.lookups + + def lookups_z_licznikiem(self, request, model_admin): + LICZNIKI["lookups_wywolan"] += 1 + wynik = oryginalne_lookups(self, request, model_admin) + + def opakowany(): + LICZNIKI["lookups_skonsumowanych"] += 1 + yield from wynik + + return opakowany() + + JednostkaFilter.lookups = lookups_z_licznikiem + + oryginalne_gci = PerRequestChangelistInstanceMixin.get_changelist_instance + + def gci_z_licznikiem(self, request): + cache = getattr(request, "_bpp_changelist_instance_cache", None) + if cache is not None and id(self) in cache: + LICZNIKI["cl_trafien"] += 1 + else: + LICZNIKI["cl_pudel"] += 1 + return oryginalne_gci(self, request) + + PerRequestChangelistInstanceMixin.get_changelist_instance = gci_z_licznikiem + + return AutorAdmin + + +def main(): + zainstaluj_liczniki() + for etykieta, fn in odkryj_scenariusze(): + if etykieta != "admin: autor (changelist)": + continue + odp = fn() + print(f"\n# {etykieta} (HTTP {odp.status_code})\n") + for nazwa, ile in LICZNIKI.items(): + print(f" {nazwa:28} {ile}") + print( + "\n (504 jednostki w bazie × liczba skonsumowanych generatorów\n" + " = liczba pobrań Jednostka.uczelnia z filtra)" + ) + return 0 + print("Nie znalazłem scenariusza") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bench/bench_orm.py b/bench/bench_orm.py new file mode 100644 index 000000000..1291fe7ba --- /dev/null +++ b/bench/bench_orm.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python +"""Benchmark ORM-a BPP: ile zapytań i ile czasu, Django 5.2 vs 6.1. + +Po co: Django 6.1 wprowadziło *fetch modes* +(``QuerySet.fetch_mode(FETCH_ONE | FETCH_PEERS | FETCH_RAISE)``). +``FETCH_PEERS`` sprawia, że pierwsze leniwe dotknięcie relacji na obiekcie +z querysetu dociąga ją **hurtem dla całego rodzeństwa** z tego samego +pobrania (``prefetch_related_objects`` pod spodem), czyli zamienia N+1 na +2 zapytania — bez deklarowania ``select_related`` z góry. + +Ten skrypt robi trzy rzeczy, w tej kolejności: + +1. ``--inwentarz`` (tylko 6.1) — podstawia *szpiegujący* ``FetchMode``, + który zachowuje się jak ``FETCH_ONE``, ale zapisuje każde leniwe + pobranie: model, pole, licznik i najbliższą ramkę kodu projektu. + To jest KROK PROFILOWANIA — inwentarz N+1 mierzony, nie zgadywany. +2. ``--pomiar`` — dla każdego scenariusza liczy zapytania (przez + ``CaptureQueriesContext``) i czas ściany, medianą z N przebiegów. +3. ``--tryb peers`` (tylko 6.1) — to samo, ale z globalnie podstawionym + ``FETCH_PEERS``, żeby zmierzyć SUFIT wygranej na całej aplikacji bez + dotykania 50 miejsc w kodzie. + +Uczciwość pomiaru (Gate 5 skilla python-performance): +* Ten sam Python (3.13), ta sama baza (kopia produkcji), ten sam kod + scenariuszy — jedyna różnica to wersja Django. +* ``CACHES["default"]`` = DummyCache, więc cache aplikacji nie schowa + pracy bazy ani po jednej, ani po drugiej stronie. +* Pierwszy przebieg każdego scenariusza jest ROZGRZEWKĄ i nie wchodzi + do statystyki — inaczej mierzylibyśmy zimny bufor PG i import modułów. +* Raportujemy medianę i rozrzut; przy rozrzucie > 10% mediany wynik jest + szumem i jest tak oznaczany. +""" + +from __future__ import annotations + +import argparse +import collections +import os +import statistics +import sys +import time +import traceback + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_bpp.settings.bench") + +import django # noqa: E402 + +django.setup() + +from django.db import connection, reset_queries # noqa: E402 +from django.test import Client # noqa: E402 +from django.test.utils import CaptureQueriesContext # noqa: E402 +from django.urls import NoReverseMatch, reverse # noqa: E402 + +WERSJA_DJANGO = django.get_version() +MA_FETCH_MODES = WERSJA_DJANGO >= "6.1" + +# Skrypty leżą w ``bench/``, a kod projektu w ``src/`` — o jeden poziom wyżej. +# Ta ścieżka służy do rozpoznawania, które ramki stosu należą do BPP +# (atrybucja leniwych pobrań), więc musi wskazywać na ``/src``. +KATALOG_PROJEKTU = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src" +) + + +# --------------------------------------------------------------------------- +# Szpieg leniwych pobrań (inwentarz N+1). Tylko Django >= 6.1. +# --------------------------------------------------------------------------- + + +def zbuduj_szpiega(): + """Zwróć ``FetchMode``, który liczy leniwe pobrania, ale ich nie blokuje. + + Dlaczego nie ``FETCH_RAISE``: on wywala się na PIERWSZYM leniwym + pobraniu, więc jeden przebieg pokazuje jedno miejsce i chowa resztę. + Szpieg przepuszcza pobranie dalej (``fetch_one``), więc jeden request + wypluwa CAŁY inwentarz naraz. + """ + from django.db.models.fetch_modes import FetchMode + + class FetchSzpieg(FetchMode): + # Bez ``track_peers`` — udajemy dokładnie zachowanie Django 5.2. + track_peers = False + + def __init__(self): + self.trafienia = collections.Counter() + self.miejsca = {} + + def fetch(self, fetcher, instance): + klucz = (type(instance).__name__, fetcher.field.name) + self.trafienia[klucz] += 1 + if klucz not in self.miejsca: + self.miejsca[klucz] = _najblizsza_ramka_projektu() + fetcher.fetch_one(instance) + + return FetchSzpieg() + + +def _najblizsza_ramka_projektu(): + """``plik:linia`` najgłębszej ramki należącej do kodu BPP. + + Leniwe pobranie wywołane z szablonu ma na stosie wyłącznie ramki + Django (``template/base.py`` itd.) — wtedy zwracamy najgłębszą ramkę + szablonową z nazwą szablonu, jeśli da się ją wyłuskać z locals. + """ + najlepsza = None + for ramka, linia in traceback.walk_stack(sys._getframe(2)): + plik = ramka.f_code.co_filename + if plik.startswith(KATALOG_PROJEKTU): + return f"{os.path.relpath(plik, KATALOG_PROJEKTU)}:{linia}" + if najlepsza is None and "django/template" in plik: + szablon = _nazwa_szablonu_z_ramki(ramka) + if szablon: + najlepsza = f"szablon {szablon}" + return najlepsza or "(tylko ramki Django)" + + +def _nazwa_szablonu_z_ramki(ramka): + for nazwa in ("self", "context"): + obj = ramka.f_locals.get(nazwa) + origin = getattr(obj, "origin", None) or getattr( + getattr(obj, "template", None), "origin", None + ) + nazwa_szablonu = getattr(origin, "template_name", None) + if nazwa_szablonu: + return nazwa_szablonu + return None + + +def ustaw_tryb_globalnie(tryb): + """Podstaw ``tryb`` jako domyślny ``FetchMode`` dla CAŁEJ aplikacji. + + Dwa miejsca, bo Django czyta domyślny tryb z dwóch niezależnych + źródeł: + + * ``query.DEFAULT_FETCH_MODE`` — czytane w ``QuerySet.__init__``, więc + dotyczy obiektów pochodzących z querysetów (czyli praktycznie + wszystkiego, co widzi widok), + * ``base.FETCH_ONE`` — domyślna wartość ``ModelState.fetch_mode`` dla + instancji powstałych POZA querysetem (``Model(...)``). + + Podstawienie ``FETCH_PEERS`` w drugim miejscu jest bezpieczne, bo + ``ModelState.peers`` ma klasowy default ``()``, a ``FetchPeers.fetch`` + przy pustym rodzeństwie degraduje do ``fetch_one``. + """ + from django.db.models import base as models_base + from django.db.models import query as models_query + + models_query.DEFAULT_FETCH_MODE = tryb + models_base.FETCH_ONE = tryb + + +# --------------------------------------------------------------------------- +# Scenariusze — prawdziwe requesty na prawdziwych danych. +# --------------------------------------------------------------------------- + + +def _url_bpp(nazwa, **kwargs): + """``reverse`` w przestrzeni ``bpp:``, albo ``None`` gdy trasy nie ma.""" + try: + return reverse(f"bpp:{nazwa}", kwargs=kwargs) + except NoReverseMatch: + return None + + +def _scenariusze_publiczne(klient): + """Publiczne strony przeglądania, na identyfikatorach WZIĘTYCH Z BAZY. + + Identyfikatory z palca dałyby 404 i mierzylibyśmy obsługę błędu. + Autora wybieramy tego z NAJWIĘKSZĄ liczbą publikacji — najostrzejszy + przypadek dla N+1 na podstronie autora. + """ + from django.db.models import Count + + from bpp.models import Autor, Jednostka, Rekord, Zrodlo + + autor = ( + Autor.objects.filter(pokazuj=True) + .annotate(ile=Count("wydawnictwo_ciagle")) + .order_by("-ile") + .first() + ) + jednostka = Jednostka.objects.filter(widoczna=True).first() + zrodlo = Zrodlo.objects.first() + rekord = Rekord.objects.exclude(rok=None).order_by("-rok").first() + rok = rekord.rok if rekord else 2024 + + pozycje = [ + ("publiczne: lista autorów", _url_bpp("browse_autorzy")), + ("publiczne: lista jednostek", _url_bpp("browse_jednostki")), + ("publiczne: lista źródeł", _url_bpp("browse_zrodla")), + ("publiczne: lista lat", _url_bpp("browse_lata")), + ("publiczne: rekordy z roku", _url_bpp("browse_rok", rok=rok)), + ] + if autor: + pozycje.append( + ("publiczne: autor (szczegóły)", _url_bpp("browse_autor", pk=autor.pk)) + ) + if jednostka: + pozycje.append( + ( + "publiczne: jednostka (szczegóły)", + _url_bpp("browse_jednostka", slug=jednostka.slug), + ) + ) + if zrodlo: + pozycje.append( + ( + "publiczne: źródło (szczegóły)", + _url_bpp("browse_zrodlo", slug=zrodlo.slug), + ) + ) + return [(e, u, klient) for e, u in pozycje] + + +def _klient_admina(): + """Zalogowany klient na DEDYKOWANYM koncie benchmarkowym. + + Świadomie NIE logujemy się na istniejące konto z dumpu produkcyjnego — + to prawdziwe dane osobowe, a benchmark nie ma powodu zostawiać śladu + w ``request.user`` cudzego konta. + """ + from django.contrib.auth import get_user_model + + Uzytkownik = get_user_model() + konto, _ = Uzytkownik.objects.get_or_create( + username="__bench_admin__", + defaults={"is_staff": True, "is_superuser": True, "is_active": True}, + ) + if not (konto.is_staff and konto.is_superuser): + konto.is_staff = konto.is_superuser = True + konto.save() + + klient = Client() + klient.force_login(konto) + return klient + + +def _scenariusze_z_nazw_tras(pary, klient): + """``(etykieta, nazwa_trasy)`` → pozycje scenariuszy, pomijając brakujące.""" + pozycje = [] + for etykieta, nazwa in pary: + try: + pozycje.append((etykieta, reverse(nazwa), klient)) + except NoReverseMatch: + continue + return pozycje + + +def odkryj_scenariusze(): + """Zbuduj listę ``(etykieta, callable)`` z danych, które SĄ w bazie.""" + anonim = Client() + admin = _klient_admina() + + pozycje = [ + *_scenariusze_publiczne(anonim), + # changelisty admina to klasyczne siedlisko N+1 (list_display po FK) + *_scenariusze_z_nazw_tras( + ( + ( + "admin: wyd. ciągłe (changelist)", + "admin:bpp_wydawnictwo_ciagle_changelist", + ), + ( + "admin: wyd. zwarte (changelist)", + "admin:bpp_wydawnictwo_zwarte_changelist", + ), + ("admin: autor (changelist)", "admin:bpp_autor_changelist"), + ("admin: źródło (changelist)", "admin:bpp_zrodlo_changelist"), + ("admin: jednostka (changelist)", "admin:bpp_jednostka_changelist"), + ), + admin, + ), + # REST API: serializery DRF chodzą po FK per obiekt + *_scenariusze_z_nazw_tras( + ( + ("api_v1: rekordy", "api_v1:rekord-list"), + ("api_v1: autorzy", "api_v1:autor-list"), + ("api_v1: źródła", "api_v1:zrodlo-list"), + ), + anonim, + ), + ] + + return [ + (etykieta, lambda u=url, k=klient: k.get(u)) + for etykieta, url, klient in pozycje + if url + ] + + +# --------------------------------------------------------------------------- +# Pomiar +# --------------------------------------------------------------------------- + + +def zmierz(fn, powtorzenia): + """Zwróć ``(mediana_ms, rozrzut_ms, zapytania, kod_http)``. + + Pierwszy przebieg to rozgrzewka — odrzucany. Bez tego mierzylibyśmy + zimne bufory PostgreSQL i leniwe importy Django, a nie pracę ORM-a. + """ + odpowiedz = fn() + kod = getattr(odpowiedz, "status_code", None) + + reset_queries() + with CaptureQueriesContext(connection) as ctx: + fn() + zapytania = len(ctx.captured_queries) + + czasy = [] + for _ in range(powtorzenia): + start = time.perf_counter() + fn() + czasy.append((time.perf_counter() - start) * 1000) + + mediana = statistics.median(czasy) + rozrzut = statistics.stdev(czasy) if len(czasy) > 1 else 0.0 + return mediana, rozrzut, zapytania, kod + + +def uruchom_pomiar(powtorzenia, tryb): + if tryb == "peers": + if not MA_FETCH_MODES: + sys.exit("--tryb peers wymaga Django >= 6.1") + from django.db.models import FETCH_PEERS + + ustaw_tryb_globalnie(FETCH_PEERS) + + scenariusze = odkryj_scenariusze() + print(f"\n# Django {WERSJA_DJANGO}, tryb={tryb}, powtórzeń={powtorzenia}\n") + print( + f"{'scenariusz':38} {'zapytań':>8} {'ms (mediana)':>13} {'±ms':>7} {'HTTP':>5}" + ) + print("-" * 76) + wyniki = {} + for etykieta, fn in scenariusze: + try: + mediana, rozrzut, zapytania, kod = zmierz(fn, powtorzenia) + except Exception as exc: # noqa: BLE001 - raportujemy i lecimy dalej + print(f"{etykieta:38} {'BŁĄD':>8} {type(exc).__name__}: {exc}"[:120]) + continue + flaga = " SZUM" if mediana and rozrzut > 0.10 * mediana else "" + print( + f"{etykieta:38} {zapytania:>8} {mediana:>13.1f} {rozrzut:>7.1f} " + f"{kod:>5}{flaga}" + ) + wyniki[etykieta] = {"zapytania": zapytania, "ms": mediana, "http": kod} + return wyniki + + +def uruchom_inwentarz(): + if not MA_FETCH_MODES: + sys.exit("--inwentarz wymaga Django >= 6.1 (fetch modes)") + + szpieg = zbuduj_szpiega() + ustaw_tryb_globalnie(szpieg) + + scenariusze = odkryj_scenariusze() + print(f"\n# INWENTARZ leniwych pobrań — Django {WERSJA_DJANGO}\n") + for etykieta, fn in scenariusze: + szpieg.trafienia.clear() + szpieg.miejsca.clear() + try: + odpowiedz = fn() + except Exception as exc: # noqa: BLE001 + print(f"\n## {etykieta}\n BŁĄD: {type(exc).__name__}: {exc}"[:200]) + continue + kod = getattr(odpowiedz, "status_code", "?") + suma = sum(szpieg.trafienia.values()) + print(f"\n## {etykieta} (HTTP {kod}) — leniwych pobrań: {suma}") + if not suma: + print( + " (brak — wszystko przez select_related/prefetch albo kolumny lokalne)" + ) + continue + for (model, pole), ile in szpieg.trafienia.most_common(12): + print(f" {ile:>6}× {model}.{pole:<28} @ {szpieg.miejsca[(model, pole)]}") + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--inwentarz", action="store_true", help="wykryj miejsca N+1 (6.1)") + p.add_argument("--pomiar", action="store_true", help="zmierz zapytania i czas") + p.add_argument("--tryb", choices=("one", "peers"), default="one") + p.add_argument("--powtorzenia", type=int, default=5) + args = p.parse_args() + + if not (args.inwentarz or args.pomiar): + p.error("podaj --inwentarz albo --pomiar") + if args.inwentarz: + uruchom_inwentarz() + if args.pomiar: + uruchom_pomiar(args.powtorzenia, args.tryb) + + +if __name__ == "__main__": + main() diff --git a/bench/bench_rownowaznosc.py b/bench/bench_rownowaznosc.py new file mode 100644 index 000000000..768f7832f --- /dev/null +++ b/bench/bench_rownowaznosc.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +"""Sprawdź, czy ``FETCH_PEERS`` NIE zmienia tego, co strona zwraca. + +Sam spadek liczby zapytań nic nie znaczy, dopóki nie wiadomo, że wynik +jest ten sam — „szybciej, ale inaczej" to nie optymalizacja, to regresja. +Dlatego dla każdego scenariusza pobieramy odpowiedź DWA razy w tym samym +procesie: raz w ``FETCH_ONE`` (zachowanie Django 5.2), raz w +``FETCH_PEERS`` — i porównujemy bajt w bajt. + +Kolejność jest ustalona (ONE, potem PEERS) i sprawdzamy też ONE↔ONE, żeby +odsiać strony niedeterministyczne z natury (CSRF token, znacznik czasu, +losowa kolejność) — inaczej ich niestabilność wyglądałaby jak wina +``FETCH_PEERS``. +""" + +from __future__ import annotations + +import os +import re +import sys + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_bpp.settings.bench") + +import django # noqa: E402 + +django.setup() + +from bench_orm import odkryj_scenariusze, ustaw_tryb_globalnie # noqa: E402 +from django.db.models import FETCH_ONE, FETCH_PEERS # noqa: E402 + +# Elementy zmienne PER REQUEST, niezależne od trybu pobierania. Każdy z nich +# został wskazany empirycznie — porównaniem dwóch odpowiedzi w tym SAMYM +# trybie (FETCH_ONE↔FETCH_ONE), więc żaden nie maskuje różnicy, którą mógłby +# wprowadzić FETCH_PEERS. Bez tej normalizacji strony admina są nieporównywalne. +WZORCE_ZMIENNE = [ + # token CSRF: pole formularza, nagłówek XHR, querystring, literał JS + (re.compile(rb'name="csrfmiddlewaretoken" value="[^"]*"'), b"CSRF-POLE"), + (re.compile(rb'X-CSRFToken", "[^"]*"'), b"CSRF-XHR"), + (re.compile(rb"csrfmiddlewaretoken=[A-Za-z0-9]+"), b"CSRF-QS"), + (re.compile(rb"csrfmiddlewaretoken: '[^']*'"), b"CSRF-JS"), + # znacznik czasu generacji strony w stopce BPP + (re.compile(rb"wygenerowano dnia [^,]+,"), b"CZAS,"), +] + + +def znormalizuj(tresc): + for wzorzec, zamiennik in WZORCE_ZMIENNE: + tresc = wzorzec.sub(zamiennik, tresc) + return tresc + + +def pobierz(fn, tryb): + ustaw_tryb_globalnie(tryb) + odp = fn() + return getattr(odp, "status_code", None), znormalizuj(getattr(odp, "content", b"")) + + +def main(): + scenariusze = odkryj_scenariusze() + print( + f"\n# RÓWNOWAŻNOŚĆ wyniku: FETCH_ONE vs FETCH_PEERS (Django {django.get_version()})\n" + ) + print(f"{'scenariusz':38} {'HTTP':>5} {'bajtów':>9} wynik") + print("-" * 84) + + ilezle = 0 + for etykieta, fn in scenariusze: + try: + kod_a, tresc_a = pobierz(fn, FETCH_ONE) + kod_a2, tresc_a2 = pobierz(fn, FETCH_ONE) + kod_b, tresc_b = pobierz(fn, FETCH_PEERS) + except Exception as exc: # noqa: BLE001 - raportujemy i lecimy dalej + print( + f"{etykieta:38} {'—':>5} {'—':>9} BŁĄD {type(exc).__name__}: {exc}"[ + :150 + ] + ) + ilezle += 1 + continue + + stabilna = tresc_a == tresc_a2 + zgodna = tresc_a == tresc_b + + if not stabilna: + # Strona sama z siebie nie jest deterministyczna — porównanie + # z PEERS nic nie powie. Porównujemy wtedy tylko długość i kod. + werdykt = ( + "NIEDETERMINISTYCZNA (ONE≠ONE); " + f"dł. ONE={len(tresc_a)} PEERS={len(tresc_b)}, HTTP zgodny={kod_a == kod_b}" + ) + elif zgodna and kod_a == kod_b: + werdykt = "OK — identyczna" + else: + werdykt = ( + f"!! ROZJECHANA (HTTP {kod_a}→{kod_b}, {len(tresc_a)}→{len(tresc_b)} B)" + ) + ilezle += 1 + + print(f"{etykieta:38} {kod_a!s:>5} {len(tresc_a):>9} {werdykt}") + + print() + if ilezle: + print(f"UWAGA: {ilezle} scenariusz(y) NIE potwierdziło równoważności.") + return 1 + print("Wszystkie deterministyczne scenariusze: wynik identyczny w obu trybach.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/deweloper/benchmark-orm.md b/docs/deweloper/benchmark-orm.md new file mode 100644 index 000000000..b7d44a20d --- /dev/null +++ b/docs/deweloper/benchmark-orm.md @@ -0,0 +1,135 @@ +# Benchmark ORM-a na kopii bazy produkcyjnej (`bench/`) + +Harness do mierzenia liczby zapytań i czasu odpowiedzi na **prawdziwych +danych**, przez prawdziwe requesty (`django.test.Client`), a nie na +mikrobenchmarkach. Powstał przy audycie Django 6.1 (*fetch modes*) i służy +dwóm rzeczom: + +1. **wykrywaniu N+1** — mierzonemu, nie zgadywanemu, +2. **weryfikacji optymalizacji** — czy spadek liczby zapytań jest realny + i czy strona nadal zwraca to samo. + +## Warunki wstępne + +* działający Docker, +* dump bazy produkcyjnej (patrz „Baza" niżej), +* Django >= 6.1 dla `--inwentarz`, `bench_atrybucja.py` i + `bench_licznik_wywolan.py` (używają *fetch modes*). `--pomiar` działa też + na 5.2. + +## Baza + +Stawiamy **własny** kontener PostgreSQL na nietypowym porcie, żeby nie +kolidować z `docker compose up db redis` ani z `run-site`: + +```bash +docker run -d --name bpp-bench-pg -p 55432:5432 \ + -e POSTGRES_USER=bpp -e POSTGRES_PASSWORD=password -e POSTGRES_DB=bpp \ + --shm-size=1g iplweb/bpp_dbserver:psql-16.13 \ + -c shared_buffers=1GB -c work_mem=64MB -c maintenance_work_mem=512MB +docker run -d --name bpp-bench-redis -p 55379:6379 redis:7-alpine +``` + +Dump produkcyjny `db-backup-*.tar.gz` to **format katalogowy** +(`pg_dump -Fd`) zapakowany w tar — NIE `.sql` ani `.dump`, więc +`run-site --from-dump` go nie przyjmie. Rozpakuj i odtwórz przez +`pg_restore`: + +```bash +gzip -dc db-backup-20260603-023000.tar.gz | tar -xf - +PGPASSWORD=password pg_restore -h localhost -p 55432 -U bpp -d bpp \ + --no-owner --no-privileges --no-comments -j 6 db-backup-20260603-023000/ +``` + +Dwa błędy `nierozpoznany parametr konfiguracyjny "transaction_timeout"` są +normalne — `pg_restore` 18 emituje GUC z PG 17+, którego PG 16 nie zna. + +Potem doprowadź schemat do HEAD (dump bywa starszy niż gałąź): + +```bash +DJANGO_SETTINGS_MODULE=django_bpp.settings.bench \ + uv run python src/manage.py migrate --noinput +``` + +## Dwa warianty konfiguracji — używaj OBU + +| moduł settings | `CACHEOPS` | co mierzy | +|---|---|---| +| `django_bpp.settings.bench` | brak reguł (jak `local.py`) | pełną pracę bazy, bez maskowania | +| `django_bpp.settings.bench_prod` | reguły **wczytane z `production.py`** | to, co realnie zobaczy wdrożenie | + +To rozróżnienie jest krytyczne, a łatwo je przeoczyć: `local.py` **nie +definiuje `CACHEOPS` wcale**, a produkcja cache'uje `bpp.uczelnia`, +`bpp.jednostka`, `bpp.tytul` i pozostałe słowniki. Pomiar tylko na +`local.py` **zawyża** zysk — liczy jako zapytania do PostgreSQL coś, co na +produkcji jest trafieniem w Redisa. Przy audycie Django 6.1 różnica była +dramatyczna: changelist autorów pokazywał 1102 → 96 zapytań bez cacheops, +a z produkcyjnymi regułami 27 → 27, czyli **zysk zerowy**. + +`bench_prod.py` czyta reguły z `production.py` przez AST (bez wykonywania +modułu), żeby nie mierzyć własnej, rozjeżdżającej się kopii. + +## Użycie + +```bash +# inwentarz N+1 — co i gdzie dociąga się leniwie (Django >= 6.1) +uv run python bench/bench_orm.py --inwentarz + +# pomiar: liczba zapytań + czas, mediana z N przebiegów +DJANGO_SETTINGS_MODULE=django_bpp.settings.bench_prod \ + uv run python bench/bench_orm.py --pomiar --powtorzenia 25 + +# sufit wygranej z FETCH_PEERS włączonym globalnie (Django >= 6.1) +uv run python bench/bench_orm.py --pomiar --tryb peers --powtorzenia 25 + +# dowód, że FETCH_PEERS nie zmienia wyniku (porównanie bajt w bajt) +uv run python bench/bench_rownowaznosc.py + +# rozbicie pobrań JEDNEGO pola na miejsca wywołania +uv run python bench/bench_atrybucja.py Jednostka.uczelnia "admin: autor (changelist)" + +# licznik budów ChangeList / konsumpcji generatora filtra +uv run python bench/bench_licznik_wywolan.py +``` + +**Przy każdej zmianie wersji Django wyczyść cacheops**, bo pikluje +instancje modeli i 6.1 czytające wpisy zapisane przez 5.2 daje +`RuntimeWarning` oraz nieważny pomiar: + +```bash +docker exec bpp-bench-redis redis-cli -n 7 FLUSHDB +``` + +## Jak czytać wyniki (i jak nie dać się oszukać) + +**Liczby zapytań są dokładne i powtarzalne.** Na nich opieraj wnioski. + +**Czasy są wiarygodne tylko na cichym hoście.** Harness oznacza flagą +`SZUM` każdy pomiar, w którym odchylenie standardowe przekracza 10% +mediany — taki wynik jest szumem, nie pomiarem. Na maszynie, gdzie równolegle +biegną testy albo inne stacki, prawie wszystko dostanie tę flagę. + +**Scenariusz kontrolny.** `admin: źródło (changelist)` nie był dotykany +żadną z dotychczasowych optymalizacji i ma stale 16 zapytań. Jego rozrzut +między przebiegami to **zmierzona podłoga szumu hosta**: jeśli „poprawiony" +scenariusz zmienił się mniej niż kontrolny, różnica nie jest realna. Przy +audycie ten kontroler pokazał skok 320 → 420 ms na niezmienionej ścieżce +kodu — i dzięki temu od razu było widać, że cały przebieg jest do wyrzucenia. + +**Cacheops ukrywa N+1 przed licznikiem zapytań, ale nie przed zegarem.** +Podstrona jednostki miała 10 zapytań przed i po `FETCH_PEERS`, a czas spadł +77 → 54 ms: 109 pobrań `Autor.tytul` było trafieniami w Redisa, więc +licznik SQL ich nie widział. Jeśli liczba zapytań się nie zmienia, a czas +tak — szukaj round-tripów do cache'u. + +## Ograniczenia + +* pojedyncze żądania sekwencyjne, bez współbieżności — nie mierzy + zachowania pod obciążeniem ani rywalizacji o połączenia, +* `CACHES["default"]` to `DummyCache` w obu wariantach, więc własny cache + BPP (`PaginatorZeZliczeniemZCache`, `cache_publiczny`) nie maskuje pracy + bazy; na produkcji część stron jest dodatkowo cache'owana, co jeszcze + zmniejsza realny wpływ optymalizacji, +* `--tryb peers` podstawia `DEFAULT_FETCH_MODE` globalnie, czyli mierzy + SUFIT wygranej, a nie stan produkcyjny (tam `FETCH_PEERS` jest włączony + tylko w `BaseBppAdminMixin`). diff --git a/src/bpp/newsfragments/benchmark-orm.doc.rst b/src/bpp/newsfragments/benchmark-orm.doc.rst new file mode 100644 index 000000000..376315f8f --- /dev/null +++ b/src/bpp/newsfragments/benchmark-orm.doc.rst @@ -0,0 +1,5 @@ +Dodano harness do mierzenia wydajności ORM-a na kopii bazy produkcyjnej +(``bench/``) wraz z dokumentacją (``docs/deweloper/benchmark-orm.md``): +liczy zapytania i czas dla prawdziwych requestów, wykrywa N+1 przez +*fetch modes* z Django 6.1 i weryfikuje, że optymalizacja nie zmieniła +wyniku strony. diff --git a/src/django_bpp/settings/bench.py b/src/django_bpp/settings/bench.py new file mode 100644 index 000000000..ea9b55004 --- /dev/null +++ b/src/django_bpp/settings/bench.py @@ -0,0 +1,53 @@ +"""Settings do benchmarku ORM-a (Django 5.2 vs 6.1) na produkcyjnym dumpie. + +Używany przez skrypty w ``bench/``. Konfiguracja DEV-ONLY — nie ładuje +się w produkcji (dziedziczy po ``local.py``). Porty i hasła da się nadpisać +przez zmienne środowiskowe (``os.environ.setdefault`` niżej). + +Celowe odstępstwa od ``local.py``, każde po to, żeby pomiar mierzył PRACĘ +ORM-a, a nie coś obok: + +* ``easyaudit`` — WYCISZONY, ale NIE usunięty z ``INSTALLED_APPS``. + Middleware dokłada INSERT do każdego requestu (produkcja go nie ma, + jest tylko w ``local.py``) i przesłoniłby różnicę w SELECT-ach szumem + z zapisów, więc middleware + hooki sygnałów gasimy. Samej APLIKACJI + ruszyć nie można: ``bpp.0470_indeksy_gin_i_funkcyjne`` deklaruje + zależność od ``easyaudit.0001_initial``, więc bez niej graf migracji + nie daje się zbudować (``NodeNotFoundError``). +* ``constance_cache`` zostaje na Redisie (własny kontener na 55379, nie + cudzy dev-owy 6379). LocMem tu NIE przejdzie: ``constance`` jawnie + odrzuca lokalny backend (``ImproperlyConfigured``: „refers to a + subclass of Django's local-memory backend … set it to a backend that + supports cross-process caching"). ``CACHES["default"]`` pozostaje + DummyCache z ``local.py`` — i tak ma zostać: wyłączony cache aplikacji + = widać całą pracę bazy, po obu stronach jednakowo. +* ``CONN_MAX_AGE = 0`` — świeże połączenie, bez efektów cieplnych puli. +""" + +import os + +os.environ.setdefault("DJANGO_BPP_SKIP_DOTENV", "1") +os.environ.setdefault("DJANGO_BPP_DB_HOST", "localhost") +os.environ.setdefault("DJANGO_BPP_DB_PORT", "55432") +os.environ.setdefault("DJANGO_BPP_DB_NAME", "bpp") +os.environ.setdefault("DJANGO_BPP_DB_USER", "bpp") +os.environ.setdefault("DJANGO_BPP_DB_PASSWORD", "password") +os.environ.setdefault("DJANGO_BPP_REDIS_PORT", "55379") + +from .local import * # noqa +from .local import CACHES, DATABASES, INSTALLED_APPS, MIDDLEWARE # noqa + +DEBUG = False +ALLOWED_HOSTS = ["*"] + +MIDDLEWARE[:] = [m for m in MIDDLEWARE if "easyaudit" not in m] +DJANGO_EASY_AUDIT_WATCH_MODEL_EVENTS = False +DJANGO_EASY_AUDIT_WATCH_AUTH_EVENTS = False +DJANGO_EASY_AUDIT_WATCH_REQUEST_EVENTS = False + +CACHES["constance_cache"] = { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://localhost:55379/8", +} + +DATABASES["default"]["CONN_MAX_AGE"] = 0 diff --git a/src/django_bpp/settings/bench_prod.py b/src/django_bpp/settings/bench_prod.py new file mode 100644 index 000000000..652e11dfb --- /dev/null +++ b/src/django_bpp/settings/bench_prod.py @@ -0,0 +1,87 @@ +"""Wariant benchmarku z PRODUKCYJNYMI regułami cacheops. + +Używany przez skrypty w ``bench/``. Konfiguracja DEV-ONLY. + +Po co osobny wariant: ``local.py`` (a więc i ``bench.py``) NIE definiuje +``CACHEOPS`` wcale, więc cacheops jest w ``INSTALLED_APPS``, ale nie +cache'uje niczego. Produkcja definiuje reguły m.in. dla ``bpp.uczelnia``, +``bpp.jednostka``, ``bpp.tytul`` i pozostałych słowników — a to DOKŁADNIE +te modele, w które uderzają znalezione N+1 (``Jednostka.__str__`` → +``self.uczelnia``, ``Autor.__str__`` → ``self.tytul``). + +Pomiar bez tych reguł ZAWYŻA zysk z ``FETCH_PEERS``: liczy jako zapytania +do PostgreSQL coś, co na produkcji jest trafieniem w Redisa. Dlatego +mierzymy oba warianty i raportujemy oba. + +Reguły są WYCIĄGANE Z ``production.py`` przez AST, a nie przepisane — +kopia rozjechałaby się z produkcją przy pierwszej zmianie reguł, a wtedy +benchmark mierzyłby konfigurację, której nikt nie ma. Nie importujemy +``production.py`` wprost, bo jego zaimportowanie wykonuje cały moduł +(strażnik ``SECRET_KEY``, ``ALLOWED_HOSTS`` z env itd.). +""" + +import ast +import pathlib + +from .bench import * # noqa +from .bench import CACHES # noqa + +_PRODUCTION_PY = pathlib.Path(__file__).with_name("production.py") + + +class _ZlozStale(ast.NodeTransformer): + """Zamień arytmetykę na stałych w jedną stałą (``60 * 60`` → ``3600``). + + Potrzebne, bo ``CACHEOPS_DEFAULTS = {"timeout": 60 * 60}`` w + ``production.py`` to ``BinOp``, a ``ast.literal_eval`` przyjmuje wyłącznie + literały i odrzuca go (``malformed node or string``). Składamy więc stałe + sami — dzięki temu NIE musimy sięgać po ``eval``/``compile``: po tym + przejściu drzewo jest już czystym literałem. + + Świadomie obsługujemy tylko cztery operatory arytmetyczne i wyłącznie na + liczbach. Cokolwiek innego przechodzi nietknięte i wywali się później na + ``literal_eval`` — czyli głośno, a nie po cichu z błędną wartością. + """ + + _OPERACJE = { + ast.Add: lambda a, b: a + b, + ast.Sub: lambda a, b: a - b, + ast.Mult: lambda a, b: a * b, + ast.FloorDiv: lambda a, b: a // b, + } + + def visit_BinOp(self, wezel): # noqa: N802 - nazwa wymagana przez ast + self.generic_visit(wezel) + operacja = self._OPERACJE.get(type(wezel.op)) + lewo, prawo = wezel.left, wezel.right + if ( + operacja is not None + and isinstance(lewo, ast.Constant) + and isinstance(prawo, ast.Constant) + and isinstance(lewo.value, (int, float)) + and isinstance(prawo.value, (int, float)) + ): + return ast.copy_location( + ast.Constant(value=operacja(lewo.value, prawo.value)), wezel + ) + return wezel + + +def _wczytaj_z_production(nazwa): + """Zwróć wartość przypisania ``nazwa = ...`` z ``production.py``. + + Parsujemy plik do AST i czytamy prawą stronę przypisania — reszta + ``production.py`` się NIE wykonuje (inaczej odpaliłby się strażnik + ``SECRET_KEY`` i odczyty ``ALLOWED_HOSTS`` z env). + """ + drzewo = ast.parse(_PRODUCTION_PY.read_text(encoding="utf-8")) + for wezel in drzewo.body: + if isinstance(wezel, ast.Assign) and any( + isinstance(cel, ast.Name) and cel.id == nazwa for cel in wezel.targets + ): + return ast.literal_eval(_ZlozStale().visit(wezel.value)) + raise RuntimeError(f"Nie znalazłem {nazwa} w {_PRODUCTION_PY}") + + +CACHEOPS = _wczytaj_z_production("CACHEOPS") +CACHEOPS_DEFAULTS = _wczytaj_z_production("CACHEOPS_DEFAULTS") From db2e62ff780704372cbc264ae1c3157a6735e106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Fri, 7 Aug 2026 14:40:36 +0200 Subject: [PATCH 07/10] docs: plan dalszych usprawnien z Django 6.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zbiera to, czego migracja na 6.1 NIE objela, a co warto rozwazyc — z dowodami z kodu, szacunkiem kosztu i warunkami wyjscia. Nie jest to lista przepisana z release notes: kazda pozycja byla sprawdzona w tym repozytorium. Osobna sekcja notuje tematy SPRAWDZONE I WYKLUCZONE (podpisane ciasteczka, PBKDF2, deprecacja list_select_related=True, GeneratedField), zeby nie wracaly w dyskusji. Zawiera tez: dlug techniczny z warunkami zdjecia (pin DRF z gita, shim itercompat, usuniety django-prometheus), deprecacje 6.1 trafiajace w kod z terminem na Django 7.0 wraz z lista miejsc, oraz opis znanego flake'a w komparator_pbn_udzialy. Dokument nie jest w nav mkdocs — tak samo jak pozostale dokumenty planowe i audytowe w docs/deweloper/. `mkdocs build --strict` przechodzi. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F134BWb3YoYPQ6zjzqoqds --- docs/deweloper/django-6.1-co-jeszcze.md | 180 ++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 docs/deweloper/django-6.1-co-jeszcze.md diff --git a/docs/deweloper/django-6.1-co-jeszcze.md b/docs/deweloper/django-6.1-co-jeszcze.md new file mode 100644 index 000000000..eb539fb73 --- /dev/null +++ b/docs/deweloper/django-6.1-co-jeszcze.md @@ -0,0 +1,180 @@ +# Django 6.1 — co jeszcze warto wziąć + +Dokument roboczy powstały przy migracji BPP z Django 5.2 LTS na 6.1 +(sierpień 2026). Zbiera to, czego migracja **nie** objęła, a co warto +rozważyć — razem z dowodami z kodu, szacunkiem kosztu i warunkami wyjścia. + +Nie jest to lista życzeń przepisana z release notes. Każda pozycja była +sprawdzona w tym repozytorium; tam, gdzie sprawdzenie wykluczyło temat, +jest to zapisane, żeby nie wracał w dyskusji. + +## Stan na wejściu + +| Zrobione | Gdzie | +|---|---| +| Migracja na Django 6.1, Python >= 3.12 | PR #731 | +| `FETCH_PEERS` w adminie (N+1 → 2 zapytania) | PR #733 | +| `delete_confirmation_max_display` | w trakcie | +| Gate regresyjny na N+1 przez `FETCH_RAISE` | w trakcie | + +## Sprawdzone i wykluczone — zero pracy + +Odhaczone, żeby nie wracały: + +- **Zmiana derywacji soli podpisanych ciasteczek.** Dotyczy wyłącznie + `set_signed_cookie`/`get_signed_cookie`, których w kodzie nie ma. + `cerif_export/oai/tokeny.py` używa gołego `signing.dumps`/`loads` + z własną solą — mechanizm nietknięty (`django/core/signing.py`, + `_cookie_signer_salt` / `_unsign_cookie`). Brak ryzyka na wdrożeniu, + brak potrzeby ustawiania `SIGNED_COOKIE_LEGACY_SALT_FALLBACK`. +- **PBKDF2 1 200 000 → 1 500 000 iteracji.** `PASSWORD_HASHERS` nie jest + nadpisane, więc wzmocnienie dziedziczy się za darmo. Hasła rehashują się + przy kolejnym logowaniu. +- **Deprecacja `ModelAdmin.list_select_related = True`.** Nie dotyczy — + 25 wystąpień, wszystkie są listami albo dictami (dialekt + `django-dynamic-admin-columns`), żadne nie jest `True`. +- **`GeneratedField` jako kolumny wirtualne.** Wymaga PostgreSQL 18, + produkcja stoi na 16. +- **`StringAgg(distinct=True)` na SQLite, nowości Oracle.** + Bezprzedmiotowe — baza to PostgreSQL. +- **Framework `Tasks` z Django.** BPP ma Celery + `django-liveops` + + `celery-singleton`; migracja nie ma uzasadnienia. + +## Kandydatury otwarte + +### 1. `MAILERS` — nie opcja, tylko termin + +**Dlaczego:** `EMAIL_BACKEND`, `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USE_TLS` +i cała rodzina są **deprecated w 6.1 i znikają w Django 7.0**. Dodatkowo +`fail_silently` przepada **bez zamiennika** — Django nie oferuje niczego +w zamian dla obsługi wyjątków. + +**Stan w kodzie:** + +- `settings/base.py` — `EMAIL_URL` przez `django-environ` + (`env.email(...)` + `vars().update(EMAIL_CONFIG)`), +- `settings/production.py:177-183` — warunkowa podmiana `EMAIL_BACKEND` + na `djcelery_email.backends.CeleryEmailBackend` plus doklejenie + `djcelery_email` do `INSTALLED_APPS` w runtime, +- 4 użycia `fail_silently=True`: `pbn_api/client/publication_sync.py` + (496, 574, 624) i `bpp/models/abstract/pbn.py:64`. + +**Zysk poza samą zgodnością:** `MAILERS` ten kod **upraszcza**. Zamiast +warunkowej podmiany backendu w runtime wystarczy drugi alias +(`"async": {...}`) i jawne `send_mail(..., using="async")` tam, gdzie +faktycznie chcemy kolejkować. Znika magia w `production.py`. + +**Uwaga:** przepisanie `fail_silently=True` na jawny `try/except` jest +zgodne z regułą projektu zakazującą cichego łykania wyjątków — więc to +nie tylko koszt, ale i porządek. + +### 2. CSP — teraz w rdzeniu Django, więc znacznie taniej + +**Dlaczego:** BPP nie ma dziś **żadnego** Content-Security-Policy. Django +6.1 daje to w rdzeniu, bez dodatkowej paczki: +`django.middleware.csp.ContentSecurityPolicyMiddleware`, ustawienia +`SECURE_CSP` / `SECURE_CSP_REPORT_ONLY`, tag szablonowy `csp_nonce_attr`, +check systemowy `security.W027` pilnujący spójności konfiguracji. +Szablony admina i wbudowane **same niosą już nonce**. + +**Koszt — nie ukrywajmy go:** ok. **68 szablonów zawiera inline +`