From 1cef64648ec06943d9ad6809d9d9eb26baa4da27 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:06:32 +0000 Subject: [PATCH 01/30] deps: migrate from poetry to uv - rewrite pyproject.toml to PEP 621 + uv dependency-groups; hatchling backend - replace poetry.lock with uv.lock; drive fixtures via `uv run` - keep existing pins (Django 3.2) so this is a pure tooling swap, no upgrades - pin django-crispy-forms==1.14.0 to match the prior resolved lock --- Makefile | 2 +- poetry.lock | 2109 ------------------------------------------------ pyproject.toml | 131 +-- uv.lock | 1527 +++++++++++++++++++++++++++++++++++ 4 files changed, 1598 insertions(+), 2171 deletions(-) delete mode 100644 poetry.lock create mode 100644 uv.lock diff --git a/Makefile b/Makefile index f1d317f9..2ba570ed 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ fixtures: export KEGBOT_BASE_URL = http://fixture.example.com/ fixtures: export KEGBOT_ENV = test fixtures: rm -f test-db.sqlite - bin/kegbot generate_fixtures + uv run bin/kegbot generate_fixtures rm -f test-db.sqlite .PHONY: fixtures \ No newline at end of file diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 7ef15387..00000000 --- a/poetry.lock +++ /dev/null @@ -1,2109 +0,0 @@ -[[package]] -name = "addict" -version = "2.4.0" -description = "Addict is a dictionary whose items can be set using both attribute and item syntax." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "alabaster" -version = "0.7.12" -description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "asgiref" -version = "3.5.2" -description = "ASGI specs, helper code, and adapters" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] - -[[package]] -name = "async-timeout" -version = "4.0.2" -description = "Timeout context manager for asyncio programs" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "attrs" -version = "22.1.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] - -[[package]] -name = "babel" -version = "2.10.3" -description = "Internationalization utilities" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pytz = ">=2015.7" - -[[package]] -name = "beautifulsoup4" -version = "4.11.1" -description = "Screen-scraping library" -category = "dev" -optional = false -python-versions = ">=3.6.0" - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "black" -version = "22.6.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2022.6.15" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "cffi" -version = "1.15.1" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "2.1.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode_backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.5" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "coloredlogs" -version = "15.0.1" -description = "Colored terminal output for Python's logging module" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -humanfriendly = ">=9.1" - -[package.extras] -cron = ["capturer (>=2.4)"] - -[[package]] -name = "confusable-homoglyphs" -version = "3.2.0" -description = "Detect confusable usage of unicode homoglyphs, prevent homograph attacks." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -cli = ["click"] - -[[package]] -name = "deprecated" -version = "1.2.13" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] - -[[package]] -name = "dj-database-url" -version = "1.0.0" -description = "Use Database URLs in your Django Application." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -Django = ">3.2" - -[[package]] -name = "dj-email-url" -version = "1.0.5" -description = "Use an URL to configure email backend settings in your Django Application." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "django" -version = "3.2.15" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -asgiref = ">=3.3.2,<4" -pytz = "*" -sqlparse = ">=0.2.2" - -[package.extras] -bcrypt = ["bcrypt"] -argon2 = ["argon2-cffi (>=19.1.0)"] - -[[package]] -name = "django-appconf" -version = "1.0.5" -description = "A helper class for handling configuration defaults of packaged apps gracefully." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -django = "*" - -[[package]] -name = "django-bootstrap-pagination" -version = "1.7.1" -description = "Render Django Page objects as Bootstrap 3.x/4.x Pagination compatible HTML" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "django-cors-headers" -version = "3.13.0" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -Django = ">=3.2" - -[[package]] -name = "django-crispy-forms" -version = "1.14.0" -description = "Best way to have Django DRY forms" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "django-imagekit" -version = "4.1.0" -description = "Automated image processing for Django models." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -django-appconf = {version = "*", markers = "python_version > \"3\""} -pilkit = ">=0.2.0" -six = "*" - -[package.extras] -async = ["django-celery (>=3.0)"] -async_dramatiq = ["django-dramatiq (>=0.4.0)"] -async_rq = ["django-rq (>=0.6.0)"] - -[[package]] -name = "django-redis" -version = "5.2.0" -description = "Full featured redis cache backend for Django." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -Django = ">=2.2" -redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1" - -[package.extras] -hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] - -[[package]] -name = "django-registration" -version = "3.3" -description = "An extensible user-registration application for Django" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -confusable-homoglyphs = ">=3.0,<4.0" -Django = ">=3.2" - -[[package]] -name = "django-rq" -version = "2.5.1" -description = "An app that provides django integration for RQ (Redis Queue)" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -django = ">=2.0" -redis = ">=3" -rq = ">=1.2" - -[package.extras] -sentry = ["raven (>=6.1.0)"] -testing = ["mock (>=2.0.0)"] - -[[package]] -name = "djangorestframework" -version = "3.13.1" -description = "Web APIs for Django, made easy." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -django = ">=2.2" -pytz = "*" - -[[package]] -name = "docutils" -version = "0.19" -description = "Docutils -- Python Documentation Utilities" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" - -[[package]] -name = "freezegun" -version = "1.2.1" -description = "Let your Python tests travel through time" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -python-dateutil = ">=2.7" - -[[package]] -name = "furo" -version = "2022.6.21" -description = "A clean customisable Sphinx documentation theme." -category = "dev" -optional = false -python-versions = ">=3.7" -develop = false - -[package.dependencies] -beautifulsoup4 = "*" -pygments = "*" -sphinx = ">=4.0,<6.0" -sphinx-basic-ng = "*" - -[package.source] -type = "git" -url = "https://github.com/Kegbot/furo" -reference = "kegbot/kegbot" -resolved_reference = "493cf3b4a44c8038c1aa0efa3d2aff8ff12b742c" - -[[package]] -name = "future" -version = "0.18.2" -description = "Clean single-source support for Python 3 and 2" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "gevent" -version = "21.12.0" -description = "Coroutine-based network library" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" - -[package.dependencies] -cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = {version = ">=1.1.0,<2.0", markers = "platform_python_implementation == \"CPython\""} -"zope.event" = "*" -"zope.interface" = "*" - -[package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] -docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] -monitor = ["psutil (>=5.7.0)"] -recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "selectors2", "backports.socketpair", "psutil (>=5.7.0)"] -test = ["requests", "objgraph", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "psutil (>=5.7.0)"] - -[[package]] -name = "greenlet" -version = "1.1.2" -description = "Lightweight in-process concurrent programming" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" - -[package.extras] -docs = ["sphinx"] - -[[package]] -name = "gunicorn" -version = "20.1.0" -description = "WSGI HTTP Server for UNIX" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -eventlet = ["eventlet (>=0.24.1)"] -gevent = ["gevent (>=1.4.0)"] -setproctitle = ["setproctitle"] -tornado = ["tornado (>=0.2)"] - -[[package]] -name = "httplib2" -version = "0.20.4" -description = "A comprehensive HTTP client library." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} - -[[package]] -name = "humanfriendly" -version = "10.0" -description = "Human friendly output for text interfaces using Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} - -[[package]] -name = "idna" -version = "3.3" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "isodate" -version = "0.6.1" -description = "An ISO 8601 date/time/duration parser and formatter" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "livereload" -version = "2.6.3" -description = "Python LiveReload is an awesome tool for web developers" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" -tornado = {version = "*", markers = "python_version > \"2.7\""} - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "multidict" -version = "6.0.2" -description = "multidict implementation" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "mysqlclient" -version = "2.1.1" -description = "Python interface to MySQL" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "oauthlib" -version = "3.2.0" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "pilkit" -version = "2.0" -description = "A collection of utilities and processors for the Python Imaging Libary." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pillow" -version = "9.2.0" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -testing = ["pytest-benchmark", "pytest"] -dev = ["tox", "pre-commit"] - -[[package]] -name = "protobuf" -version = "3.20.0" -description = "Protocol Buffers" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "psycopg2" -version = "2.9.3" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycodestyle" -version = "2.9.1" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyflakes" -version = "2.5.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pygments" -version = "2.12.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["railroad-diagrams", "jinja2"] - -[[package]] -name = "pyreadline3" -version = "3.4.1" -description = "A python implementation of GNU readline." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pytest" -version = "7.1.2" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-django" -version = "4.5.2" -description = "A Django plugin for pytest." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -pytest = ">=5.4.0" - -[package.extras] -docs = ["sphinx", "sphinx-rtd-theme"] -testing = ["django", "django-configurations (>=2.0)"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-gflags" -version = "3.1.2" -description = "Obsolete. Please migrate to absl-py instead." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pytz" -version = "2022.1" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyyaml" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "redis" -version = "4.3.4" -description = "Python client for Redis database and key-value store" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -async-timeout = ">=4.0.2" -deprecated = ">=1.2.3" -packaging = ">=20.4" - -[package.extras] -hiredis = ["hiredis (>=1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-mock" -version = "1.9.3" -description = "Mock out responses from the requests package" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -requests = ">=2.3,<3" -six = "*" - -[package.extras] -fixture = ["fixtures"] -test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"] - -[[package]] -name = "requests-oauthlib" -version = "1.3.1" -description = "OAuthlib authentication support for Requests." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - -[[package]] -name = "rq" -version = "1.11.0" -description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -click = ">=5.0.0" -redis = ">=3.5.0" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "soupsieve" -version = "2.3.2.post1" -description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "sphinx" -version = "5.1.1" -description = "Python documentation generator" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=1.3" -colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.20" -imagesize = "*" -Jinja2 = ">=2.3" -packaging = "*" -Pygments = ">=2.0" -requests = ">=2.5.0" -snowballstemmer = ">=1.1" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "flake8-comprehensions", "flake8-bugbear", "isort", "mypy (>=0.971)", "sphinx-lint", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] - -[[package]] -name = "sphinx-autobuild" -version = "2021.3.14" -description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = "*" -livereload = "*" -sphinx = "*" - -[package.extras] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "sphinx-basic-ng" -version = "0.0.1a12" -description = "A modern skeleton for Sphinx themes." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -sphinx = ">=4.0,<6.0" - -[package.extras] -docs = ["furo", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs", "ipython"] - -[[package]] -name = "sphinx-issues" -version = "3.0.1" -description = "A Sphinx extension for linking to your project's issue tracker" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -sphinx = "*" - -[package.extras] -dev = ["pytest (>=6.2.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.11.1)", "pre-commit (>=2.7,<3.0)", "tox"] -lint = ["flake8 (==3.9.2)", "flake8-bugbear (==20.11.1)", "pre-commit (>=2.7,<3.0)"] -tests = ["pytest (>=6.2.0)"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.2" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -test = ["pytest"] -lint = ["docutils-stubs", "mypy", "flake8"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -test = ["pytest"] -lint = ["docutils-stubs", "mypy", "flake8"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.0" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -test = ["html5lib", "pytest"] -lint = ["docutils-stubs", "mypy", "flake8"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -test = ["mypy", "flake8", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -test = ["pytest"] -lint = ["docutils-stubs", "mypy", "flake8"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest"] - -[[package]] -name = "sqlparse" -version = "0.4.2" -description = "A non-validating SQL parser." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tornado" -version = "6.2" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "dev" -optional = false -python-versions = ">= 3.7" - -[[package]] -name = "urllib3" -version = "1.26.11" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "vcrpy" -version = "4.2.0" -description = "Automatically mock your HTTP interactions to simplify and speed up testing" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -PyYAML = "*" -six = ">=1.5" -wrapt = "*" -yarl = "*" - -[[package]] -name = "whitenoise" -version = "6.2.0" -description = "Radically simplified static file serving for WSGI applications" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -brotli = ["brotli"] - -[[package]] -name = "wrapt" -version = "1.14.1" -description = "Module for decorators, wrappers and monkey patching." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "yarl" -version = "1.8.1" -description = "Yet another URL library" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[[package]] -name = "zope.event" -version = "4.5.0" -description = "Very basic event publishing system" -category = "main" -optional = false -python-versions = "*" - -[package.extras] -docs = ["sphinx"] -test = ["zope.testrunner"] - -[[package]] -name = "zope.interface" -version = "5.4.0" -description = "Interfaces for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -docs = ["sphinx", "repoze.sphinx.autointerface"] -test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] -testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.10.4" -content-hash = "1e70a1452915a5f3a870a2589a8e3a5568bba208d16a5f59df014bce67fadfec" - -[metadata.files] -addict = [ - {file = "addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc"}, - {file = "addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494"}, -] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -asgiref = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, -] -async-timeout = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -babel = [ - {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, - {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, -] -beautifulsoup4 = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, -] -black = [ - {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, - {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, - {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"}, - {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"}, - {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"}, - {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"}, - {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"}, - {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"}, - {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"}, - {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"}, - {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"}, - {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"}, - {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"}, - {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"}, - {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"}, - {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"}, - {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"}, - {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"}, - {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"}, - {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"}, - {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"}, - {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"}, - {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"}, -] -certifi = [ - {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, - {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, -] -cffi = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, - {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -coloredlogs = [ - {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, - {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, -] -confusable-homoglyphs = [ - {file = "confusable_homoglyphs-3.2.0-py2.py3-none-any.whl", hash = "sha256:e3ce611028d882b74a5faa69e3cbb5bd4dcd9f69936da6e73d33eda42c917944"}, - {file = "confusable_homoglyphs-3.2.0.tar.gz", hash = "sha256:3b4a0d9fa510669498820c91a0bfc0c327568cecec90648cf3819d4a6fc6a751"}, -] -deprecated = [ - {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, - {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, -] -dj-database-url = [ - {file = "dj-database-url-1.0.0.tar.gz", hash = "sha256:ccf3e8718f75ddd147a1e212fca88eecdaa721759ee48e38b485481c77bca3dc"}, - {file = "dj_database_url-1.0.0-py3-none-any.whl", hash = "sha256:cd354a3b7a9136d78d64c17b2aec369e2ae5616fbca6bfbe435ef15bb372ce39"}, -] -dj-email-url = [ - {file = "dj-email-url-1.0.5.tar.gz", hash = "sha256:ef36f8a324ec57cf3be5c7a7ef44ed6900ca0208624a918ab33adc1cf6427b39"}, - {file = "dj_email_url-1.0.5-py2.py3-none-any.whl", hash = "sha256:64257c4f9d8139a4af8e5267229d32260e433fbf257b0cf8fc855bb0cc39ca7d"}, -] -django = [ - {file = "Django-3.2.15-py3-none-any.whl", hash = "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713"}, - {file = "Django-3.2.15.tar.gz", hash = "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"}, -] -django-appconf = [ - {file = "django-appconf-1.0.5.tar.gz", hash = "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4"}, - {file = "django_appconf-1.0.5-py3-none-any.whl", hash = "sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d"}, -] -django-bootstrap-pagination = [ - {file = "django-bootstrap-pagination-1.7.1.tar.gz", hash = "sha256:69d826d92217325611cb86e49944d8261e3c92eaa4deafea5a605d79fd363883"}, - {file = "django_bootstrap_pagination-1.7.1-py3-none-any.whl", hash = "sha256:47e742679cf109e12f10ae09d5263433f94440818cf7b023e90a3f5252849639"}, -] -django-cors-headers = [ - {file = "django-cors-headers-3.13.0.tar.gz", hash = "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"}, - {file = "django_cors_headers-3.13.0-py3-none-any.whl", hash = "sha256:37e42883b5f1f2295df6b4bba96eb2417a14a03270cb24b2a07f021cd4487cf4"}, -] -django-crispy-forms = [ - {file = "django-crispy-forms-1.14.0.tar.gz", hash = "sha256:35887b8851a931374dd697207a8f56c57a9c5cb9dbf0b9fa54314da5666cea5b"}, - {file = "django_crispy_forms-1.14.0-py3-none-any.whl", hash = "sha256:bc4d2037f6de602d39c0bc452ac3029d1f5d65e88458872cc4dbc01c3a400604"}, -] -django-imagekit = [ - {file = "django-imagekit-4.1.0.tar.gz", hash = "sha256:e559aeaae43a33b34f87631a9fa5696455e4451ffa738a42635fde442fedac5c"}, - {file = "django_imagekit-4.1.0-py2.py3-none-any.whl", hash = "sha256:87e36f8dc1d8745647881f4366ef4965225f048042dacebbee0dcb87425defef"}, -] -django-redis = [ - {file = "django-redis-5.2.0.tar.gz", hash = "sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de"}, - {file = "django_redis-5.2.0-py3-none-any.whl", hash = "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026"}, -] -django-registration = [ - {file = "django-registration-3.3.tar.gz", hash = "sha256:884a4cc9ec87b9f1c0ceb6b6c4b7ba491c1877997a3cd29cc923697dac785eb8"}, - {file = "django_registration-3.3-py3-none-any.whl", hash = "sha256:dfa176f594fb465c93495caa55686be723a15829769511383e25172d2efbd0e6"}, -] -django-rq = [ - {file = "django-rq-2.5.1.tar.gz", hash = "sha256:f08486602664d73a6e335872c868d79663e380247e6307496d01b8fa770fefd8"}, - {file = "django_rq-2.5.1-py2.py3-none-any.whl", hash = "sha256:7be1e10e7091555f9f36edf100b0dbb205ea2b98683d74443d2bdf3c6649a03f"}, -] -djangorestframework = [ - {file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"}, - {file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"}, -] -docutils = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] -flake8 = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] -freezegun = [ - {file = "freezegun-1.2.1-py3-none-any.whl", hash = "sha256:15103a67dfa868ad809a8f508146e396be2995172d25f927e48ce51c0bf5cb09"}, - {file = "freezegun-1.2.1.tar.gz", hash = "sha256:b4c64efb275e6bc68dc6e771b17ffe0ff0f90b81a2a5189043550b6519926ba4"}, -] -furo = [] -future = [ - {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, -] -gevent = [ - {file = "gevent-21.12.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:2afa3f3ad528155433f6ac8bd64fa5cc303855b97004416ec719a6b1ca179481"}, - {file = "gevent-21.12.0-cp27-cp27m-win32.whl", hash = "sha256:177f93a3a90f46a5009e0841fef561601e5c637ba4332ab8572edd96af650101"}, - {file = "gevent-21.12.0-cp27-cp27m-win_amd64.whl", hash = "sha256:a5ad4ed8afa0a71e1927623589f06a9b5e8b5e77810be3125cb4d93050d3fd1f"}, - {file = "gevent-21.12.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:eae3c46f9484eaacd67ffcdf4eaf6ca830f587edd543613b0f5c4eb3c11d052d"}, - {file = "gevent-21.12.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1899b921219fc8959ff9afb94dae36be82e0769ed13d330a393594d478a0b3a"}, - {file = "gevent-21.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21cb5c9f4e14d75b3fe0b143ec875d7dbd1495fad6d49704b00e57e781ee0f"}, - {file = "gevent-21.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:542ae891e2aa217d2cf6d8446538fcd2f3263a40eec123b970b899bac391c47a"}, - {file = "gevent-21.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0082d8a5d23c35812ce0e716a91ede597f6dd2c5ff508a02a998f73598c59397"}, - {file = "gevent-21.12.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da8d2d51a49b2a5beb02ad619ca9ddbef806ef4870ba04e5ac7b8b41a5b61db3"}, - {file = "gevent-21.12.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfff82f05f14b7f5d9ed53ccb7a609ae8604df522bb05c971bca78ec9d8b2b9"}, - {file = "gevent-21.12.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7909780f0cf18a1fc32aafd8c8e130cdd93c6e285b11263f7f2d1a0f3678bc50"}, - {file = "gevent-21.12.0-cp36-cp36m-win32.whl", hash = "sha256:bb5cb8db753469c7a9a0b8a972d2660fe851aa06eee699a1ca42988afb0aaa02"}, - {file = "gevent-21.12.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c43f081cbca41d27fd8fef9c6a32cf83cb979345b20abc07bf68df165cdadb24"}, - {file = "gevent-21.12.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:74fc1ef16b86616cfddcc74f7292642b0f72dde4dd95aebf4c45bb236744be54"}, - {file = "gevent-21.12.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc2fef0f98ee180704cf95ec84f2bc2d86c6c3711bb6b6740d74e0afe708b62c"}, - {file = "gevent-21.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08b4c17064e28f4eb85604486abc89f442c7407d2aed249cf54544ce5c9baee6"}, - {file = "gevent-21.12.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:973749bacb7bc4f4181a8fb2a7e0e2ff44038de56d08e856dd54a5ac1d7331b4"}, - {file = "gevent-21.12.0-cp37-cp37m-win32.whl", hash = "sha256:6a02a88723ed3f0fd92cbf1df3c4cd2fbd87d82b0a4bac3e36a8875923115214"}, - {file = "gevent-21.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f289fae643a3f1c3b909d6b033e6921b05234a4907e9c9c8c3f1fe403e6ac452"}, - {file = "gevent-21.12.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3baeeccc4791ba3f8db27179dff11855a8f9210ddd754f6c9b48e0d2561c2aea"}, - {file = "gevent-21.12.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05c5e8a50cd6868dd36536c92fb4468d18090e801bd63611593c0717bab63692"}, - {file = "gevent-21.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d86438ede1cbe0fde6ef4cc3f72bf2f1ecc9630d8b633ff344a3aeeca272cdd"}, - {file = "gevent-21.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01928770972181ad8866ee37ea3504f1824587b188fcab782ef1619ce7538766"}, - {file = "gevent-21.12.0-cp38-cp38-win32.whl", hash = "sha256:3c012c73e6c61f13c75e3a4869dbe6a2ffa025f103421a6de9c85e627e7477b1"}, - {file = "gevent-21.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:b7709c64afa8bb3000c28bb91ec42c79594a7cb0f322e20427d57f9762366a5b"}, - {file = "gevent-21.12.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:ec21f9eaaa6a7b1e62da786132d6788675b314f25f98d9541f1bf00584ed4749"}, - {file = "gevent-21.12.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ce1f38fdfe2149ffe8ec2131ca45281791c1e464db34b3b4321ae9d8d2efbb"}, - {file = "gevent-21.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ccffcf708094564e442ac6fde46f0ae9e40015cb69d995f4b39cc29a7643881"}, - {file = "gevent-21.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24d3550fbaeef5fddd794819c2853bca45a86c3d64a056a2c268d981518220d1"}, - {file = "gevent-21.12.0-cp39-cp39-win32.whl", hash = "sha256:2bcec9f80196c751fdcf389ca9f7141e7b0db960d8465ed79be5e685bfcad682"}, - {file = "gevent-21.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:3dad62f55fad839d498c801e139481348991cee6e1c7706041b5fe096cb6a279"}, - {file = "gevent-21.12.0-pp27-pypy_73-win_amd64.whl", hash = "sha256:9f9652d1e4062d4b5b5a0a49ff679fa890430b5f76969d35dccb2df114c55e0f"}, - {file = "gevent-21.12.0.tar.gz", hash = "sha256:f48b64578c367b91fa793bf8eaaaf4995cb93c8bc45860e473bf868070ad094e"}, -] -greenlet = [ - {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, - {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, - {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, - {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, - {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, - {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, - {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, - {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, - {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, - {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, - {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, - {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"}, - {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, - {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, - {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, - {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, - {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, - {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, - {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, - {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, - {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"}, - {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, - {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, - {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, - {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, - {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"}, - {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, - {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, - {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, - {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, - {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"}, - {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, - {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, - {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, - {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, - {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"}, - {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, - {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, - {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, -] -gunicorn = [ - {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, - {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, -] -httplib2 = [ - {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, - {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, -] -humanfriendly = [ - {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, - {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -imagesize = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -isodate = [ - {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, - {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, -] -jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -livereload = [ - {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, -] -markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] -mccabe = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] -multidict = [ - {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, - {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, - {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, - {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, - {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, - {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, - {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, - {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, - {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, - {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, - {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, - {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, - {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, - {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, - {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, - {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, - {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, - {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, - {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, - {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, - {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, - {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, - {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, - {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, - {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, - {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, - {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, - {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, - {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, - {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, - {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, - {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, - {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, - {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, - {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, - {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, - {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, - {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, - {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, - {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, - {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, - {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, - {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, - {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, - {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, - {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, - {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, - {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, - {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, - {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, - {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, - {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, - {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, - {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, - {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, - {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, - {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, - {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, - {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -mysqlclient = [ - {file = "mysqlclient-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37"}, - {file = "mysqlclient-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b"}, - {file = "mysqlclient-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c"}, - {file = "mysqlclient-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994"}, - {file = "mysqlclient-2.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855"}, - {file = "mysqlclient-2.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"}, - {file = "mysqlclient-2.1.1.tar.gz", hash = "sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782"}, -] -oauthlib = [ - {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, - {file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] -pilkit = [ - {file = "pilkit-2.0.tar.gz", hash = "sha256:ddb30c2f0198a147e56b151476c3bb9fe045fbfd5b0a0fa2a3148dba62d1559f"}, -] -pillow = [ - {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, - {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, - {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, - {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, - {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, - {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, - {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, - {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, - {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, - {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, - {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, - {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, - {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, - {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, - {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, - {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, - {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, - {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -protobuf = [ - {file = "protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b"}, - {file = "protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499"}, - {file = "protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab"}, - {file = "protobuf-3.20.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc14037281db66aa60856cd4ce4541a942040686d290e3f3224dd3978f88f554"}, - {file = "protobuf-3.20.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47257d932de14a7b6c4ae1b7dbf592388153ee35ec7cae216b87ae6490ed39a3"}, - {file = "protobuf-3.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fbcbb068ebe67c4ff6483d2e2aa87079c325f8470b24b098d6bf7d4d21d57a69"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:542f25a4adf3691a306dcc00bf9a73176554938ec9b98f20f929a044f80acf1b"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd7133b885e356fa4920ead8289bb45dc6f185a164e99e10279f33732ed5ce15"}, - {file = "protobuf-3.20.0-cp37-cp37m-win32.whl", hash = "sha256:8d84453422312f8275455d1cb52d850d6a4d7d714b784e41b573c6f5bfc2a029"}, - {file = "protobuf-3.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:52bae32a147c375522ce09bd6af4d2949aca32a0415bc62df1456b3ad17c6001"}, - {file = "protobuf-3.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25d2fcd6eef340082718ec9ad2c58d734429f2b1f7335d989523852f2bba220b"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:88c8be0558bdfc35e68c42ae5bf785eb9390d25915d4863bbc7583d23da77074"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38fd9eb74b852e4ee14b16e9670cd401d147ee3f3ec0d4f7652e0c921d6227f8"}, - {file = "protobuf-3.20.0-cp38-cp38-win32.whl", hash = "sha256:7dcd84dc31ebb35ade755e06d1561d1bd3b85e85dbdbf6278011fc97b22810db"}, - {file = "protobuf-3.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:1eb13f5a5a59ca4973bcfa2fc8fff644bd39f2109c3f7a60bd5860cb6a49b679"}, - {file = "protobuf-3.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d24c81c2310f0063b8fc1c20c8ed01f3331be9374b4b5c2de846f69e11e21fb"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8be43a91ab66fe995e85ccdbdd1046d9f0443d59e060c0840319290de25b7d33"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a53d4035427b9dbfbb397f46642754d294f131e93c661d056366f2a31438263"}, - {file = "protobuf-3.20.0-cp39-cp39-win32.whl", hash = "sha256:32bf4a90c207a0b4e70ca6dd09d43de3cb9898f7d5b69c2e9e3b966a7f342820"}, - {file = "protobuf-3.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:6efe066a7135233f97ce51a1aa007d4fb0be28ef093b4f88dac4ad1b3a2b7b6f"}, - {file = "protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983"}, - {file = "protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702"}, -] -psycopg2 = [ - {file = "psycopg2-2.9.3-cp310-cp310-win32.whl", hash = "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362"}, - {file = "psycopg2-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"}, - {file = "psycopg2-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56"}, - {file = "psycopg2-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305"}, - {file = "psycopg2-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2"}, - {file = "psycopg2-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461"}, - {file = "psycopg2-2.9.3-cp38-cp38-win32.whl", hash = "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7"}, - {file = "psycopg2-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf"}, - {file = "psycopg2-2.9.3-cp39-cp39-win32.whl", hash = "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126"}, - {file = "psycopg2-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c"}, - {file = "psycopg2-2.9.3.tar.gz", hash = "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycodestyle = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, -] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] -pyflakes = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] -pygments = [ - {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, - {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pyreadline3 = [ - {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, - {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, -] -pytest = [ - {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, - {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, -] -pytest-django = [ - {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, - {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -python-gflags = [ - {file = "python-gflags-3.1.2.tar.gz", hash = "sha256:40ae131e899ef68e9e14aa53ca063839c34f6a168afe622217b5b875492a1ee2"}, -] -pytz = [ - {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, - {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -redis = [ - {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, - {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -requests-mock = [ - {file = "requests-mock-1.9.3.tar.gz", hash = "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"}, - {file = "requests_mock-1.9.3-py2.py3-none-any.whl", hash = "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970"}, -] -requests-oauthlib = [ - {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, - {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, -] -rq = [ - {file = "rq-1.11.0-py2.py3-none-any.whl", hash = "sha256:c0bbf898b56817da053cdc992ab46da2729a7ccd70dd50316ad9209b7d7b71c1"}, - {file = "rq-1.11.0.tar.gz", hash = "sha256:50d0cf687cfb2530eac9396c7426e420958a166e8f4666bd2096bdcf7f4ad03e"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -snowballstemmer = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] -soupsieve = [ - {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, - {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, -] -sphinx = [ - {file = "Sphinx-5.1.1-py3-none-any.whl", hash = "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693"}, - {file = "Sphinx-5.1.1.tar.gz", hash = "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89"}, -] -sphinx-autobuild = [ - {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, - {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, -] -sphinx-basic-ng = [ - {file = "sphinx_basic_ng-0.0.1a12-py3-none-any.whl", hash = "sha256:e8b6efd2c5ece014156de76065eda01ddfca0fee465aa020b1e3c12f84570bbe"}, - {file = "sphinx_basic_ng-0.0.1a12.tar.gz", hash = "sha256:cffffb14914ddd26c94b1330df1d72dab5a42e220aaeb5953076a40b9c50e801"}, -] -sphinx-issues = [ - {file = "sphinx-issues-3.0.1.tar.gz", hash = "sha256:b7c1dc1f4808563c454d11c1112796f8c176cdecfee95f0fd2302ef98e21e3d6"}, - {file = "sphinx_issues-3.0.1-py3-none-any.whl", hash = "sha256:8b25dc0301159375468f563b3699af7a63720fd84caf81c1442036fcd418b20c"}, -] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, -] -sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] -sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, -] -sphinxcontrib-jsmath = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] -sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] -sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] -sqlparse = [ - {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, - {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -tornado = [ - {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, - {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, - {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, - {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, - {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, -] -urllib3 = [ - {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, - {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, -] -vcrpy = [ - {file = "vcrpy-4.2.0-py2.py3-none-any.whl", hash = "sha256:7ec280c8d5385652f1117fe32a200e6676614007d9f946af9f07df1e5f92254c"}, - {file = "vcrpy-4.2.0.tar.gz", hash = "sha256:94520b86fb765925adc8c77ff934e89a5e156c28e74a314320217ef1b454afe0"}, -] -whitenoise = [ - {file = "whitenoise-6.2.0-py3-none-any.whl", hash = "sha256:8e9c600a5c18bd17655ef668ad55b5edf6c24ce9bdca5bf607649ca4b1e8e2c2"}, - {file = "whitenoise-6.2.0.tar.gz", hash = "sha256:8fa943c6d4cd9e27673b70c21a07b0aa120873901e099cd46cab40f7cc96d567"}, -] -wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, -] -yarl = [ - {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, - {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, - {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, - {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, - {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, - {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, - {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, - {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, - {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, - {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, - {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, - {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, - {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, -] -"zope.event" = [ - {file = "zope.event-4.5.0-py2.py3-none-any.whl", hash = "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42"}, - {file = "zope.event-4.5.0.tar.gz", hash = "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"}, -] -"zope.interface" = [ - {file = "zope.interface-5.4.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a"}, - {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531"}, - {file = "zope.interface-5.4.0-cp27-cp27m-win32.whl", hash = "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325"}, - {file = "zope.interface-5.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e"}, - {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004"}, - {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117"}, - {file = "zope.interface-5.4.0-cp35-cp35m-win32.whl", hash = "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8"}, - {file = "zope.interface-5.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63"}, - {file = "zope.interface-5.4.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9"}, - {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2"}, - {file = "zope.interface-5.4.0-cp36-cp36m-win32.whl", hash = "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78"}, - {file = "zope.interface-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1"}, - {file = "zope.interface-5.4.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8"}, - {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf"}, - {file = "zope.interface-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7"}, - {file = "zope.interface-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94"}, - {file = "zope.interface-5.4.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48"}, - {file = "zope.interface-5.4.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4"}, - {file = "zope.interface-5.4.0-cp38-cp38-win32.whl", hash = "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb"}, - {file = "zope.interface-5.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54"}, - {file = "zope.interface-5.4.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1"}, - {file = "zope.interface-5.4.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c"}, - {file = "zope.interface-5.4.0-cp39-cp39-win32.whl", hash = "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e"}, - {file = "zope.interface-5.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09"}, - {file = "zope.interface-5.4.0.tar.gz", hash = "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e"}, -] diff --git a/pyproject.toml b/pyproject.toml index 7f5e618d..d8de99c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,68 @@ +[project] +name = "kegbot" +version = "1.3.0" +description = "Kegbot Server" +authors = [{ name = "The Kegbot Project contributors" }] +license = "MIT" +requires-python = ">=3.10.4,<4" +dependencies = [ + "addict", + "coloredlogs", + "dj-database-url", + "dj-email-url", + "django-bootstrap-pagination", + "django-cors-headers>=3.13.0,<4", + "django-crispy-forms==1.14.0", + "django-imagekit", + "django-redis", + "django-registration", + "django-rq>=2.5.1,<3", + "djangorestframework>=3.13.1,<4", + "Django==3.2.15", + "fakeredis", + "freezegun>=1.2.1,<2", + "future>=0.18.2,<0.19", + "gevent", + "gunicorn", + "httplib2", + "isodate", + "mysqlclient", + "oauthlib", + "Pillow", + "pilkit", + "protobuf==3.20.0", + "psycopg2", + "python-gflags", + "pytz", + "PyYAML", + "redis", + "requests", + "requests-oauthlib", + "rq>=1.10.1,<2", + "whitenoise", +] + +[project.urls] +repository = "https://github.com/Kegbot/kegbot-server" + +[dependency-groups] +dev = [ + "black", + "flake8", + "isort", + "pytest", + "pytest-django", + "requests-mock", + "vcrpy", +] + +[tool.uv] +package = true + +# mysqlclient/psycopg2 are source-only (need system libs); keep them in the +# lock for CI/Docker but skip locally where those libs are absent, e.g.: +# uv sync --no-install-package mysqlclient --no-install-package psycopg2 + [tool.black] line-length = 100 target_version = ['py310'] @@ -8,65 +73,9 @@ exclude = ''' ) ''' -[tool.poetry] -name = "kegbot" -version = "1.3.0" -description = "Kegbot Server" -authors = ["The Kegbot Project contributors"] -repository = "https://github.com/Kegbot/kegbot-server" -license = "MIT" -packages = [ - { include = "pykeg", from = "." } -] - -[tool.poetry.dependencies] -python = "^3.10.4" -coloredlogs = "*" -dj-database-url = "*" -dj-email-url = "*" -django-bootstrap-pagination = "*" -django-crispy-forms = "*" -django-imagekit = "*" -django-redis = "*" -django-registration = "*" -gunicorn = "*" -httplib2 = "*" -isodate = "*" -oauthlib = "*" -pilkit = "*" -protobuf = "3.20.0" -python-gflags = "*" -pytz = "*" -redis = "*" -requests-oauthlib = "*" -requests = "*" -whitenoise = "*" -Django = "3.2.15" -Pillow = "*" -PyYAML = "*" -mysqlclient = "*" -addict = "*" -gevent = "*" -psycopg2 = "*" -future = "^0.18.2" -freezegun = "^1.2.1" -djangorestframework = "^3.13.1" -django-cors-headers = "^3.13.0" -rq = "^1.10.1" -django-rq = "^2.5.1" - -[tool.poetry.dev-dependencies] -black = "*" -flake8 = "*" -pytest = "*" -pytest-django = "*" -requests-mock = "*" -sphinx-issues = "*" -sphinx = "*" -vcrpy = "*" -furo = {git = "https://github.com/Kegbot/furo", rev = "kegbot/kegbot"} -sphinx-autobuild = "^2021.3.14" - [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["pykeg"] diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..e4d78554 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1527 @@ +version = 1 +revision = 3 +requires-python = ">=3.10.4, <4" + +[[package]] +name = "addict" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186, upload-time = "2020-11-21T16:21:31.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" }, +] + +[[package]] +name = "asgiref" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, +] + +[[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 = "black" +version = "26.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/84/b3f55026206a9e8820a91503308075ca48eadc515e436731ca01dbe043b3/black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", size = 1987719, upload-time = "2026-05-18T17:05:02.757Z" }, + { url = "https://files.pythonhosted.org/packages/c6/34/7db312c5e5783d6e76cffd9d5ac8972a32badae4c6e3288dac0eed8d3bed/black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", size = 1810083, upload-time = "2026-05-18T17:05:04.302Z" }, + { url = "https://files.pythonhosted.org/packages/33/e2/e0101e73c2c8727634e2efcb35e2b34bd23ad70dfa673789f5773a591b21/black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", size = 1860633, upload-time = "2026-05-18T17:05:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4c/e15c0c5b23cf3651035fe5addcce90e283af3548a3f91bb03d81b83106ab/black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", size = 1477886, upload-time = "2026-05-18T17:05:07.96Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3f/59d43ade98d2ce5c8dc34a4e46cbecd177e6d55d7d4092969c6003ccc655/black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", size = 1277111, upload-time = "2026-05-18T17:05:09.473Z" }, + { url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, + { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, +] + +[[package]] +name = "confusable-homoglyphs" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/10/1358fca1ee2d97d4f2877df9ffbe6d124da666fef3b2f75e771a4c1afee6/confusable_homoglyphs-3.3.1.tar.gz", hash = "sha256:b995001c9b2e1b4cea0cf5f3840a7c79188a8cbbad053d693572bd8c1c1ec460", size = 325480, upload-time = "2024-01-30T10:10:27.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/6e/c0fcbb7d341a46cf4241a6aa9e6a737734f0657521fc1bcd074953fe4eea/confusable_homoglyphs-3.3.1-py2.py3-none-any.whl", hash = "sha256:84c92cb79dc7f55aa290d0762b2349abd8dee4c16fbe6f99eac978d394e2e6a1", size = 144755, upload-time = "2024-01-30T10:10:24.857Z" }, +] + +[[package]] +name = "dj-database-url" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/48/51f398a47c197f584b3445de886986ddc40de18bdb6e168f325a8d2c7bca/dj_database_url-2.2.0.tar.gz", hash = "sha256:9f9b05058ddf888f1e6f840048b8d705ff9395e3b52a07165daa3d8b9360551b", size = 10874, upload-time = "2024-05-28T12:43:28.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/9a/13f173c716d07283661e821f7e1624d0904835151b4f099687455dbef81e/dj_database_url-2.2.0-py3-none-any.whl", hash = "sha256:3e792567b0aa9a4884860af05fe2aa4968071ad351e033b6db632f97ac6db9de", size = 7764, upload-time = "2024-05-28T12:44:25.836Z" }, +] + +[[package]] +name = "dj-email-url" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/ef/8eb478accd9b0369d46a98d1b43027ee0c254096149265c78e6b2e2fa3b0/dj-email-url-1.0.6.tar.gz", hash = "sha256:55ffe3329e48f54f8a75aa36ece08f365e09d61f8a209773ef09a1d4760e699a", size = 15590, upload-time = "2022-09-24T11:04:50.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/f9/fcb9745099d821f9a26092d3d6f4df8f10049885045c3a93ff726d2e40a6/dj_email_url-1.0.6-py2.py3-none-any.whl", hash = "sha256:cbd08327fbb08b104eac160fb4703f375532e4c0243eb230f5b960daee7a96db", size = 6296, upload-time = "2022-09-24T11:04:47.191Z" }, +] + +[[package]] +name = "django" +version = "3.2.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "pytz" }, + { name = "sqlparse" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/45/371717cbed904aa95864cf2b28beece8e73f6314f1d2b26cba4c2890dbeb/Django-3.2.15.tar.gz", hash = "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b", size = 9833828, upload-time = "2022-08-03T07:38:21.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/f9/9ddc8444397ed7e72c52f63b48ecc2849ae1ca4d621776399a81e501ee3c/Django-3.2.15-py3-none-any.whl", hash = "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713", size = 7888258, upload-time = "2022-08-03T07:38:15.296Z" }, +] + +[[package]] +name = "django-appconf" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/a2/e58bec8d7941b914af52a67c35b5709eceed2caa2848f28437f1666ed668/django_appconf-1.2.0.tar.gz", hash = "sha256:15a88d60dd942d6059f467412fe4581db632ef03018a3c183fb415d6fc9e5cec", size = 16127, upload-time = "2025-11-08T15:46:27.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/e6/4c34d94dfb74bbcbc489606e61f1924933de30d22c593dd1f429f35fbd7f/django_appconf-1.2.0-py3-none-any.whl", hash = "sha256:b81bce5ef0ceb9d84df48dfb623a32235d941c78cc5e45dbb6947f154ea277f4", size = 6500, upload-time = "2025-11-08T15:46:25.957Z" }, +] + +[[package]] +name = "django-bootstrap-pagination" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/af/dbf3ad602a9ab111a7709e4a5a916a7ecac92b37ea28b33ca00104770a93/django-bootstrap-pagination-1.7.1.tar.gz", hash = "sha256:69d826d92217325611cb86e49944d8261e3c92eaa4deafea5a605d79fd363883", size = 15843, upload-time = "2019-06-13T21:31:39.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/47/eb84f1b4cf6e06a18d3e666a6123301de84dea4af19e2e13edcbeaa83f4a/django_bootstrap_pagination-1.7.1-py3-none-any.whl", hash = "sha256:47e742679cf109e12f10ae09d5263433f94440818cf7b023e90a3f5252849639", size = 15815, upload-time = "2019-06-13T21:31:37.146Z" }, +] + +[[package]] +name = "django-cors-headers" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/f4/8b8c3d5e9a0aeea43576fbe623599052a7699abb54378ddb44adb1ef1ed3/django_cors_headers-3.14.0.tar.gz", hash = "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739", size = 24892, upload-time = "2023-02-25T07:20:18.41Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/f1/972b3a183192841b811e4b7496de0a31c09ae1d83a5391743c8ae0cbd86e/django_cors_headers-3.14.0-py3-none-any.whl", hash = "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e", size = 13187, upload-time = "2023-02-25T07:20:16.376Z" }, +] + +[[package]] +name = "django-crispy-forms" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/96/56854b9f0b437454cef5dd5e9977017a81c23e235eceb6f7ea9c7848d06d/django-crispy-forms-1.14.0.tar.gz", hash = "sha256:35887b8851a931374dd697207a8f56c57a9c5cb9dbf0b9fa54314da5666cea5b", size = 71229, upload-time = "2022-01-25T08:08:35.943Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/34/f5240bcecb9c0d87fc339edf66f87d2ca5580e995ca966a93fdfb5ba7a66/django_crispy_forms-1.14.0-py3-none-any.whl", hash = "sha256:bc4d2037f6de602d39c0bc452ac3029d1f5d65e88458872cc4dbc01c3a400604", size = 133307, upload-time = "2022-01-25T08:08:34.739Z" }, +] + +[[package]] +name = "django-imagekit" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django-appconf" }, + { name = "pilkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/5f/229c58eefe2a9051bc17152179c46002b519371001ad15097a50226e1306/django_imagekit-6.1.0.tar.gz", hash = "sha256:60fa411eaf5d2d137e1d18d17b3004a6d17b482ca46b3e4b5121a5338a8fc9ff", size = 65550, upload-time = "2026-02-22T18:17:03.951Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/4f/9a3b70941982efb80fc59880a471cf26818879a00f930d80d82342cf63c1/django_imagekit-6.1.0-py3-none-any.whl", hash = "sha256:b735620d101821ae0789480b3945addfeb900f0d7653c7f377e0dc1ed63d7c39", size = 41061, upload-time = "2026-02-22T18:17:02.622Z" }, +] + +[[package]] +name = "django-redis" +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/9d/2272742fdd9d0a9f0b28cd995b0539430c9467a2192e4de2cea9ea6ad38c/django-redis-5.4.0.tar.gz", hash = "sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42", size = 52567, upload-time = "2023-10-01T20:22:01.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/f1/63caad7c9222c26a62082f4f777de26389233b7574629996098bf6d25a4d/django_redis-5.4.0-py3-none-any.whl", hash = "sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b", size = 31119, upload-time = "2023-10-01T20:21:33.009Z" }, +] + +[[package]] +name = "django-registration" +version = "3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "confusable-homoglyphs" }, + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/00/ee3e5e08cb16475836f9ad117631f598f62a4e32a37c01ab4cb0859454ea/django-registration-3.4.tar.gz", hash = "sha256:1a0ccef7ef71e67a78a551abd8ad378977dc14a036f1fcd8be422a68bd5254a9", size = 89040, upload-time = "2023-07-04T01:25:38.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/3a/455455a208cd38c7fececec136e0de4a848004a7dafe4d62e55566dcbbfe/django_registration-3.4-py3-none-any.whl", hash = "sha256:fa76df481189794f47eb73043ee5cc9bfb0963194b901d7bd8cf914beab1ddd0", size = 96588, upload-time = "2023-07-04T01:25:36.714Z" }, +] + +[[package]] +name = "django-rq" +version = "2.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "redis" }, + { name = "rq" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/65/3727ab9f92c780fb23e8c9939097384fb5ff66e6127a4225b58c57f8051c/django-rq-2.10.3.tar.gz", hash = "sha256:572f2e142a4e4a2f68b3601eef6ddfb041c470dfc7028c975981f7bb942e184c", size = 49043, upload-time = "2024-11-03T09:08:43.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/bb/f3822670a8cf7d815aa8b7f61d02190731697cbf1bd4cbe0db5bf5a1f122/django_rq-2.10.3-py2.py3-none-any.whl", hash = "sha256:f73da09708caf4abc4b16800acf762a00e59885eb8f7a94a83bf06ee2a40b26b", size = 62466, upload-time = "2024-11-03T09:08:41.126Z" }, +] + +[[package]] +name = "djangorestframework" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/60/cc2dd985400293fe7bf3fa1b9a5d61f5b44200c33f7d31952f2c9fd79e8a/djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1", size = 1066194, upload-time = "2024-03-22T15:50:51.875Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/7e/8c45ea7f85dd5d52ceddbacc6f56ecaca21ecbfc0e8c34c95618a14d5082/djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6", size = 1067096, upload-time = "2024-03-22T15:50:48.759Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fakeredis" +version = "2.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "redis" }, + { name = "sortedcontainers" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/34/73256495885f68b125b6a95f6a8448e103adf34825746ab8905db52183a0/fakeredis-2.37.0.tar.gz", hash = "sha256:7461f124dcba04a80691d72270b3d1d5cd100ef14dc068c76db825940f3ed799", size = 236339, upload-time = "2026-07-22T20:04:53.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/e2/964e6ef372770dd7c32f9738b50ff4924f1d3cccd665b568680e4bcb0167/fakeredis-2.37.0-py3-none-any.whl", hash = "sha256:657a2a695a1123be0c13f98db409371497bd94c29d260dd76a9fc7ce1a633745", size = 151526, upload-time = "2026-07-22T20:04:51.979Z" }, +] + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + +[[package]] +name = "freezegun" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/dd/23e2f4e357f8fd3bdff613c1fe4466d21bfb00a6177f238079b17f7b1c84/freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a", size = 35914, upload-time = "2025-08-09T10:39:08.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/2e/b41d8a1a917d6581fc27a35d05561037b048e47df50f27f8ac9c7e27a710/freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2", size = 19266, upload-time = "2025-08-09T10:39:06.636Z" }, +] + +[[package]] +name = "future" +version = "0.18.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/2e/cf6accf7415237d6faeeebdc7832023c90e0282aa16fd3263db0eb4715ec/future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307", size = 840896, upload-time = "2023-01-13T03:15:17.607Z" } + +[[package]] +name = "gevent" +version = "26.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, + { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, + { name = "zope-event" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/5c/92002455a57cb3634383e2b822e3bccf409f43cde34528e46428971475cf/gevent-26.7.0.tar.gz", hash = "sha256:5b333a556e38a302b1b8c80525bef16d437e16f1e7767947789406841856a102", size = 6729213, upload-time = "2026-07-22T20:16:04.713Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/60/878d0cdef05d952ac7f17ffe385143fb0f3720afce0f6ff5ddbf7aac0342/gevent-26.7.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:80e98fc808bd9cc5c911d78a443d214bf0c8f96c9fdd296893df7e40364d5f37", size = 2198781, upload-time = "2026-07-22T16:48:28.588Z" }, + { url = "https://files.pythonhosted.org/packages/69/79/6ce781b60049060e9d89b3d0fe60940353adeb39856aaad5ee925fd127e9/gevent-26.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf4b946b47cc6fdbdf9221f891db9a44df92166435c027760ee7dbdfb4039adc", size = 2229318, upload-time = "2026-07-22T17:02:11.713Z" }, + { url = "https://files.pythonhosted.org/packages/6d/38/48898f35c2092d699b755b01918551358db72b554c63f553c8f027d2bf31/gevent-26.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:55ce0b7f87f9befcc788d77eb039b1de89a35f37afc31942e12c7ae090a563b8", size = 1700480, upload-time = "2026-07-22T16:26:16.794Z" }, + { url = "https://files.pythonhosted.org/packages/93/51/53370896942523c333699394ccad379d186648dbfb913f42ce094ddfb4b3/gevent-26.7.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7f7143823ef99bc657534a2b6e8cbadedc910750cc0b4f4b4438a58d9fe43ab2", size = 1783048, upload-time = "2026-07-22T18:11:25.996Z" }, + { url = "https://files.pythonhosted.org/packages/ba/56/5a2cb36d75d3b626d6ffa116673b34442bf5afd6f7ab4b98e512c1b008d6/gevent-26.7.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:ca4019899830471910129968251c795c8aee59e225fd16326ae01c1f93f3cfa6", size = 1880257, upload-time = "2026-07-22T18:10:40.919Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/ee7eb2f03a38a4f33b0f327bab16d3158b3a794588a84dba739cbf4a3e68/gevent-26.7.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e4042da317a96d12110831cc404855f0c501a5a5aa476a7a18c3b480a5a59233", size = 1819378, upload-time = "2026-07-22T18:29:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/5e/aa/e2a202c03cff4f49bba54cc321c3afc29eee9d6fc452f4aa94d2f00e7d3d/gevent-26.7.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:5c97ca98e1aae427a267eae0fbfe8d0884327e6b1cd51fc2ef6642b8b0b82701", size = 2136837, upload-time = "2026-07-22T16:48:29.939Z" }, + { url = "https://files.pythonhosted.org/packages/ac/64/4892fbca47aa4e06b86aef96d6146bd61175b23b7db431ec7937d73b2f72/gevent-26.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5d5d1864bc3db92d1f82d1790395eda99f98b47fd9f7ec02c4e182d7828a8251", size = 1794058, upload-time = "2026-07-22T18:07:10.644Z" }, + { url = "https://files.pythonhosted.org/packages/21/23/90bb7d0c6f59d2973bb8f4bd3164be00e466823dea01568e0cb36f2afb77/gevent-26.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15fd2d88ed5370f8084079758758df91f26d2f68575e1ee76fce604ddba83e5e", size = 2159797, upload-time = "2026-07-22T17:02:13.229Z" }, + { url = "https://files.pythonhosted.org/packages/a3/67/4d1e315ee3052530fa8537e0cdf1f9c3a6606740f7372f420c7d12d5cf82/gevent-26.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:514bda3fff741d7e5ab108ee1d31550a7f4b2fd3dc6e3b6f38dfb8685efdafaa", size = 1682414, upload-time = "2026-07-22T16:26:22.18Z" }, + { url = "https://files.pythonhosted.org/packages/02/b1/d1b1de89677ee39e641ad8501ed72c2b99f1ddba1c14c462675f40b37a66/gevent-26.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:0f26f9a8c32ac0a73f6084c59b63deeacb350e7f1fee5301d95c5e0683a390d4", size = 1562794, upload-time = "2026-07-22T16:27:53.205Z" }, + { url = "https://files.pythonhosted.org/packages/2b/66/104590ad3a9e671b3ef77ad19c1cc50e7f1c8c220b27ddfaf34e5f88bd9c/gevent-26.7.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:92f256285fb43a57f152bd2e51a59cde1cd0b20869ae1e6da583b6beab88ed8a", size = 2953977, upload-time = "2026-07-22T16:23:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/64/07/350d87161378633714184828bdc57c66f9b525eca5249ad1294dc7f8cf58/gevent-26.7.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0e4fea187c5df7168b9538b4f543fcb0fcbaeb93be3d6cd499c324652c740704", size = 1800960, upload-time = "2026-07-22T18:11:27.242Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6c/ddfe298c2ecb1cfc72a03dd69ba751759f7e7835d3135f171b284eb09ed1/gevent-26.7.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:ce732fe08d0ea65de07eff6e46bade8ac6a6fdb65cc748c713f3d31ae122529e", size = 1900387, upload-time = "2026-07-22T18:10:42.973Z" }, + { url = "https://files.pythonhosted.org/packages/f9/89/2647bbbf1da35a1c271a54452e76065308cbaf684ee32a79f989ca4265f6/gevent-26.7.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:c25b3522072137aecf3389031039230190038f888e257f490b3897d0e0620f74", size = 1848046, upload-time = "2026-07-22T18:29:08.074Z" }, + { url = "https://files.pythonhosted.org/packages/31/52/4f9b4c536b5a0424e328d5a5466640d185020f1f0b08b2de0ca939cc0a0b/gevent-26.7.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:eaaa75c9014df3f8c310c64f53f1152af8c6be32e82734396bed91e1d0e6f35c", size = 2132200, upload-time = "2026-07-22T16:48:31.203Z" }, + { url = "https://files.pythonhosted.org/packages/42/88/1daffa63b257c381df68018e4d3da72b429955cf95a171a46d3220422c8d/gevent-26.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f1956032a9926ac9b4152b2a50bc5a2cc020722ec16928ccaf32e227ee0aae47", size = 1814237, upload-time = "2026-07-22T18:07:12.438Z" }, + { url = "https://files.pythonhosted.org/packages/73/4c/b996cdddca78eac435195cd97dff590c65a9e0052640bcb6f8b6f1e30b1d/gevent-26.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d989a1ad6cc54f5c69bb7304360f98b4fda80da2b773f1047db9fba61ae7379a", size = 2157938, upload-time = "2026-07-22T17:02:14.887Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/44419d7559a95e238ee45d29df30bad78a91d343cb27b13a6af552b907bb/gevent-26.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:e0c9ce2d80fc0f8894d748a1045ff26ad188e294bad656b29839271800827c85", size = 1685319, upload-time = "2026-07-22T16:26:13.954Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7a/7df1762ccd9a40ce1ca626f50cb7a75758f2c930aabcc73c91cf00cb3448/gevent-26.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:959effe0c56cdee0bf761e5c4e78ab62880be147a2f2aa31112ca2f7e5754e53", size = 1558945, upload-time = "2026-07-22T16:26:56.737Z" }, + { url = "https://files.pythonhosted.org/packages/75/63/0fcfbe3f5696e56424f331ec41e0e447cea79c384848d6019e3f7f340f4b/gevent-26.7.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b1b89eb5566f75aa8b2bbdb0308e1ac8d9113ca7cff85b45366aea9faad639a1", size = 2976844, upload-time = "2026-07-22T16:24:39.275Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6c/ea2d0afbe760c18df5bd1631dbe5a73d840d9b141cb71e6810157c2ae28a/gevent-26.7.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:449857ce058183442e2d71d83ff0c587a3ddff631e93c6d19a6dffb4814eccad", size = 1802332, upload-time = "2026-07-22T18:11:29.155Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/36f2258f1bfe8601224f6159066103b832c31e1451ce8dc2cd2408b6ecf4/gevent-26.7.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:8260a3f38b05fcf3c283417b18617562dbec74f5784f748e4ba3866789d7f3a4", size = 1901253, upload-time = "2026-07-22T18:10:44.402Z" }, + { url = "https://files.pythonhosted.org/packages/dd/38/86dd67e5c2dfab016a9c935b4338d6cf8f9bfa72dc5a0f3fb879d993127a/gevent-26.7.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:30894398d06747b433c8923a6a77ede61259ce6822a99f6c6e7fa0216ccb73c3", size = 1850489, upload-time = "2026-07-22T18:29:09.694Z" }, + { url = "https://files.pythonhosted.org/packages/f1/33/f5651942a5967483298b6ce6f45572d33120dd0fd01c8991d3fca5b1e8ee/gevent-26.7.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0b753522498118c9489753de7c612d4baed0edf384d9df2bf9492233ba1c20ff", size = 2129813, upload-time = "2026-07-22T16:48:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/c0/09/abe8217a8fcd3f0e94c9eec024a5499c0f267cb269ccea6c0e2e812319b9/gevent-26.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:055a643026dc28daff2be228555a2097937448cc9b58307edebcf81b9d78ff4b", size = 1815121, upload-time = "2026-07-22T18:07:13.988Z" }, + { url = "https://files.pythonhosted.org/packages/40/d6/dbae1cd2d27b62664cefa086035530eb21203d45b12f466272441c048c9c/gevent-26.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e1dc6a2712de67fd210e1f1a408601f6908b042f6420e188106f2f37f94ec71", size = 2155913, upload-time = "2026-07-22T17:02:16.35Z" }, + { url = "https://files.pythonhosted.org/packages/fc/42/90b662f4eb27d7727d4619d5c6be872117f1a9f187b243ec7f6fef988ce7/gevent-26.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:44e5280296129c0915addaefdb37d6e9bc124a77a433b1b1c8ddf1853c53f4e7", size = 1682483, upload-time = "2026-07-22T16:26:30.492Z" }, + { url = "https://files.pythonhosted.org/packages/e1/84/7297c56b9fff463c4ba2f685dbb913a855df903046dc68d14e8655a29ffe/gevent-26.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f08b1aa6729f794409ca137e25f671e0d9bbda4451200c5e28a769375365388", size = 1556053, upload-time = "2026-07-22T16:26:14.365Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bb/ab60d496cbdc0293ebbd6c2070b34da0632bd7a2ca20163c17e18d2d2dc9/gevent-26.7.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:0e0e3bf7ae0f82dbc5c6be26b4781e86c97f1e28d516b7a9746ac8b04bcc6948", size = 2992503, upload-time = "2026-07-22T16:24:36.503Z" }, + { url = "https://files.pythonhosted.org/packages/5c/35/75f27c06a82a5b22600aaccbd9567d89bb4091be43e96c02981f10aff23d/gevent-26.7.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:740050b53048207b080a1e183a377c47809ad0b7b7b0cd7eab0dea1045f7e480", size = 1809173, upload-time = "2026-07-22T18:11:30.724Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5f/a6b32b4db3fa76bd8a070f0f46f5306123bf6336e7a0ca0cd2f9b99473df/gevent-26.7.0-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:67983607eb6c7bafa362c5c43b69a27145b936c34a3d6441ed42413d62fae0a6", size = 1906630, upload-time = "2026-07-22T18:10:45.836Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/832495d8fcc05ff7432f038b7c4decbd5632425a2cd5da2ce73cb2d800c4/gevent-26.7.0-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:475848518d708e07d1987c3d94cb8ff53e2b3a69df32e39feda2779cafe400b0", size = 1855278, upload-time = "2026-07-22T18:29:11.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/2f36c0fa389fa2b7ceb5a8972b0e7da7bc770f9135315cf4246c607ca5fc/gevent-26.7.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f8ed457dd616bfe6682569f92730f9ab45aafb1aeca5e80eb2f6b9a2ce26d11", size = 2136155, upload-time = "2026-07-22T16:48:33.865Z" }, + { url = "https://files.pythonhosted.org/packages/b5/98/09f2cfaa23dbce48e3271e95b0d003f93acece6b5cfd40f4cebe3850d79b/gevent-26.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15373c68cf1fa14114bec2f09b16e2c65374bd5309e897e0a28740b09ce329e0", size = 1822108, upload-time = "2026-07-22T18:07:15.397Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d8/05a294165c17569f04284ad3c889684c8780544885b4cdf77b1432947d0c/gevent-26.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:73f3d53f2f390369e290c933b75bd87f1f2261f2f2f2175aa667c43ee3049bad", size = 2162814, upload-time = "2026-07-22T17:02:18.066Z" }, + { url = "https://files.pythonhosted.org/packages/59/89/58a545c4eda33e106d6887a0387adc2249abc14c779e3eb88bbfdf3768d6/gevent-26.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:f11b558d544ad2249029ba023cd6519ec3a0eee54a3d027e6515c1eaa322422a", size = 1706971, upload-time = "2026-07-22T16:27:02.263Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cd/413f293e54961e5c89c54235370e3603ec0f561e7ace8357980410efbf78/gevent-26.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:3871f4ca59ec2328c3ef638a0fe01a28a825443a133368dc78eb5ceadcad7609", size = 1585078, upload-time = "2026-07-22T16:30:48.145Z" }, + { url = "https://files.pythonhosted.org/packages/21/3a/47f29f632aaa38aa12410f57f1732fc50bfd4d4006d2e7e022ce731cabc9/gevent-26.7.0-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:3e3d6e20a94239ad353b776e72b8ce18c35dbe4e98c279aef3932651553d8404", size = 2996208, upload-time = "2026-07-22T16:23:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b3/4620f1ce81ecec9890229806c73f07dd022e40f76a552bd430391e7316c4/gevent-26.7.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:ddbd3cc76b9bc69df651a216c2a62fc6415ad463b3ac9c6cbbbb8b7b8224af17", size = 1811545, upload-time = "2026-07-22T18:11:32.224Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/d285212ffd5585d511299e13e61d76262def8e826e9f20c92cb85df406f2/gevent-26.7.0-cp315-cp315-manylinux_2_28_ppc64le.whl", hash = "sha256:01ceab7e608dc1b9859d9511a0a29d7ce2e7d909ab19fddc860e70a2ed5b10ce", size = 1910418, upload-time = "2026-07-22T18:10:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e0/c5d666e6065652918cfb6e6a3cf8f721d0e152c57cec217ad81792a1323b/gevent-26.7.0-cp315-cp315-manylinux_2_28_s390x.whl", hash = "sha256:2e6c917b2b8baeb6080797a6b25e35e1fd784319a05bb92b87c53546e5578eb2", size = 1857891, upload-time = "2026-07-22T18:29:13.13Z" }, + { url = "https://files.pythonhosted.org/packages/a1/67/e945ed458fa98b34572876bfd0d35fe4fa3f1159f43660b71d982b7cb63e/gevent-26.7.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:df75a1748b26030f2f7f10042cc45640b22954d9d0dc6b4b6f0dbe0b6751a2d4", size = 2138121, upload-time = "2026-07-22T16:48:35.358Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/622468fa1a3c4cf51f20e14813f5cc1592fe6e44a42ccca9195a0b18c769/gevent-26.7.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:ee1b389587e5d5c1eb19d0455b5b4d7a0fb5c5287af4e226ec66d9dfd2548107", size = 1825114, upload-time = "2026-07-22T18:07:16.667Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7a/151a2afcacf487ca25faf8b1bdd6c5b4ace2f7c1e6b4eaffe0a5e6e1df61/gevent-26.7.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c2918641ba756f46aa01ab9dd82d6dfceec403c77c2787298746b411dcf0288e", size = 2165990, upload-time = "2026-07-22T17:02:19.817Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/74/b13368064b09053253555d3f2839cc2684d22d5aed0d2ccffbf7a6736558/greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20", size = 206538, upload-time = "2026-07-22T12:47:14.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/9d/58f80897f4121f5c218bb931cf6d3b6514873f02ad0b729f744352926b9f/greenlet-3.5.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ac5bf81d79d2c8eeb2ef6359b2e1687a1e9ebf46c2b1f970da9a9255df51d190", size = 293072, upload-time = "2026-07-22T11:38:14.299Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9f/b4bc9bbd6a7855cbd8ad8a83c874eeeca56c24de9132b3323f81c03a30ba/greenlet-3.5.4-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89f3738167bab8c1084b94e23023d41d247117ac149fa0fbcb5bd4cf6262b353", size = 609393, upload-time = "2026-07-22T12:26:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/05/0e/744b5e063af127d2e3c74fe0f1aef15573064c83b6066883524f5b258b17/greenlet-3.5.4-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9a5e3406e3ed8125ae1a3b37c12f3434e2b1f0fa053197c5557895b4fb09606", size = 622750, upload-time = "2026-07-22T12:28:59.546Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5c/53d6b94742a6f1ee1877c7ff76262c909e137f3f7383ce96a8ab78e1ae31/greenlet-3.5.4-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a2d614cb2372c7101a12ea8b96dd56f81c986d247c5a73db67063f3ed1ca4a52", size = 629659, upload-time = "2026-07-22T12:43:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6b/d78ea2908e8e08985348f28ac396c2950be7ab66321dfe0054c73bd1f456/greenlet-3.5.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ab9f0704bccf6d3b38e0d2130b7b33271cff11453690da074fa280c3aa8e8e7", size = 622920, upload-time = "2026-07-22T11:51:06.83Z" }, + { url = "https://files.pythonhosted.org/packages/f2/34/957fc5577180ef2f57be82580ee1f59fdefad4f6c623c7d5e1b6980a76fb/greenlet-3.5.4-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:188e4d142f243051d92a1f5c244a741da02dddc070a0620c842804d7b56d008c", size = 425580, upload-time = "2026-07-22T12:39:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/3ce7009c948920b01527f8d9da29f501a31ac3d98318829e981fd879b850/greenlet-3.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2cdaadc3d31445a8f782bde3cd37e49a2c2a9c6da6daf76a3e34c683b271a3c7", size = 1582262, upload-time = "2026-07-22T12:25:01.131Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4c/0408366102a33829f7bdd6a992dad75abbf75e86cc1e76caf19e57311d29/greenlet-3.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70bdfacdc183dac838b2a0aaff2dd6134a457c52fe68a9c6bbab435483d2b9df", size = 1648906, upload-time = "2026-07-22T11:51:08.627Z" }, + { url = "https://files.pythonhosted.org/packages/13/52/ebfe8f6a1aeb8e430540b406c844ecc4e3367072b0192f69dcb85eeeec2b/greenlet-3.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:69173331fbc5d64bfac0065d7e22c39cfcd089e9b18d125bdcd5079363b09616", size = 246036, upload-time = "2026-07-22T11:38:30.073Z" }, + { url = "https://files.pythonhosted.org/packages/61/16/71eefcf68267bbf06a9b6bff57d0b222e49432326e85d74348b67694b8d4/greenlet-3.5.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e883de250e299654b1f1680f72a1a9f9ba62c9bd1bce84099c90657349a8dfbb", size = 294266, upload-time = "2026-07-22T11:37:56.142Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/a0b19adfc35d07e10acb626e9d22a3893b95f1309c42c4a20161dec16800/greenlet-3.5.4-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32802705c2c1ff25e8237b3bdacf2594fa02be80af8a66703eb7853ea7e68686", size = 613712, upload-time = "2026-07-22T12:26:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a121978b3337407d05a1ce5f79b4aa5998a43a9d8422f9726029b90b4471/greenlet-3.5.4-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57aa201b351f7c7c75627c60d29e4d5b97a07d37efeb62b903466fca42c097d7", size = 625582, upload-time = "2026-07-22T12:29:00.814Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4a/f301f1d85c69a86b90b5d581a73e8927bba4e79450037e6e2cbca05eb4fd/greenlet-3.5.4-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9667862a2e38ad379f11b845daeda22c8989186def44f06962c9c4c05e556da7", size = 633429, upload-time = "2026-07-22T12:43:42.073Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/080f16cf870e929e592f55767f01d6c98d2ee83bfdc36c3b892f2d0459ab/greenlet-3.5.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3fe76c2cac86b4f7a1e92865ac0a54384deb05c92986287c1a7110d9bd53071", size = 624663, upload-time = "2026-07-22T11:51:08.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2e/26884072b0eb343a4d5fee903341bfe5171b32b7f14553886e2b6349135a/greenlet-3.5.4-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:ae53534b5dec0f4c2ec26f898f538dc8ea1ca3ef2927d597a9439e40a09da937", size = 428238, upload-time = "2026-07-22T12:39:49.973Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/8f3ca88370b817369008faeceeee85970adc16c92a70a3e5fe5fea495a57/greenlet-3.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e1a4a684b16c45ba324e60b32a4386a87722bcb815d2a149d2182f9b401ca72", size = 1585010, upload-time = "2026-07-22T12:25:02.539Z" }, + { url = "https://files.pythonhosted.org/packages/51/c2/45877154689709ebce9a0b83c2235e6ca0f31577889b02af308c8cc5f8fb/greenlet-3.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e849e6e139b9671adeac505f72fc05f4af7fd1921faef40295e214fc3b361b59", size = 1651283, upload-time = "2026-07-22T11:51:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/8711a75cb61d85246277c07ff6e1a6504621ba473d808c11ad225ffca43f/greenlet-3.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc418cf4c873357964d6624445ed09472e50def990c65dd4e76fc3ba8cd9cef6", size = 246434, upload-time = "2026-07-22T11:43:15.557Z" }, + { url = "https://files.pythonhosted.org/packages/00/62/e290b3bce433da8f0324ac02da0b128d683482229f1a8b789fa47818a4cd/greenlet-3.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:c38c902a0986eba1f6e7ba1ab39ad5195926abde90f3fe080e08212db62176da", size = 244990, upload-time = "2026-07-22T11:39:22.626Z" }, + { url = "https://files.pythonhosted.org/packages/f3/04/81bd731d6d1e3a469d9a4c36f5eb069bcf0cbb2d5d342c9fec22245b91fc/greenlet-3.5.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3d66250e8b09f182ede05490998c818b5961f7a3640332d44c4927caec7bbfe4", size = 295909, upload-time = "2026-07-22T11:38:09.261Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dd/f5f22903a6ae70f5ea328ed0beaec92ad903f0e3b7d2845133b354abc4b8/greenlet-3.5.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c90e930c9c192e5b3ee9fb8bcd920ea3926155e2e3ded39fc697323addecee17", size = 612011, upload-time = "2026-07-22T12:26:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/8e/10/92a4a88d12b915d74ea5b6d288e4afefda4771647caa34442c156f7a454f/greenlet-3.5.4-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:791fdfeeb9c6e0c7b10fa151bf110d2a6974866f13dcb5b1c7efae698245893a", size = 624299, upload-time = "2026-07-22T12:29:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f9/03e26be3487c5238e81f2b84714959a86ea8515a869828cf41f4fc54b34e/greenlet-3.5.4-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b7c895310363f310361e0fe2072af85269d2a2a285cd04c0c59e79a5e3670dcf", size = 629603, upload-time = "2026-07-22T12:43:43.456Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/0b14bb9db2989f32cd9fe7f76afedea01ee8bee3f87c07e69f24adfe7e63/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f88193799d43dbf8c8a806d6405c9c52fe2af40bf75072a606357b33cc336c7f", size = 621541, upload-time = "2026-07-22T11:51:09.464Z" }, + { url = "https://files.pythonhosted.org/packages/57/6b/7c55ca72ef80d57c16c4a55210f82582622462dc4485799a30f4ec6f3372/greenlet-3.5.4-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:13b980043cb1b3134e81ea469da1250ddcc6bfe6d245bbaa59168d9cdc8f228f", size = 432554, upload-time = "2026-07-22T12:39:51.379Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/25e9a2d9eb6b2e8b7ca4e80a3a26cb887cce6c8e0a87c921164f11bc5574/greenlet-3.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7a5f095767c4493afcd06067f2bb3b8716e3f3f9e92b99c88e7e99f885b3d4d", size = 1581444, upload-time = "2026-07-22T12:25:03.818Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/4c9bf2e2c408dcc0556edce69efa9f802e82223573c53240136a086821f1/greenlet-3.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:42afdc1ab5f66da8c586c32af9224a74a706b4f0ea0dc3a4188a0860a09c65c9", size = 1645842, upload-time = "2026-07-22T11:51:12.295Z" }, + { url = "https://files.pythonhosted.org/packages/b5/41/303ecb26a3a56122c0f4d4073ee078881847bd6b6f463ae0ec57ec20223b/greenlet-3.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:60149df8f462d1b230038e6590c23c3b4768bb5d6c022b3b6e82532b34b0b8a3", size = 247169, upload-time = "2026-07-22T11:38:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e3/ef56864b4c35fcb3eb3b41b869f6cc46f4cd3f5e2c68e74acde8ac433951/greenlet-3.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:77d6ce04fed0d9aeed42e0f37923cc43eba9b027bdd9c34546bb4ccd143d0fe0", size = 245565, upload-time = "2026-07-22T11:38:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/c0/9a/e51225dcd58713f16ccbdcc501a8da21098ea14515b7870f1f94459e5ff5/greenlet-3.5.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:24e61b88cb7e1b1d794b32a10cc346ac779681d6d74ff137a3e0a444d2bf1f02", size = 294831, upload-time = "2026-07-22T11:38:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ea/de50a50fadf979713ab18b46f22ad5ff5f2dcfc637a3ebdecf669801e1a5/greenlet-3.5.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:870d730fec833f5a06906a32596cc099b9161594642a92a520b7a88911c95356", size = 614619, upload-time = "2026-07-22T12:26:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/2aae27fea41205b8650294c301f042a2a4bb6155eea48c995b890a92f2c1/greenlet-3.5.4-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec5ff0d1878df6af3bf9b638a5a92a7d5693291de77c91bff10fa48519c604ef", size = 627021, upload-time = "2026-07-22T12:29:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/1b/80/fb4d4788bbc8e54761f1fc88533af9523a6e86299fa113d6e8a8503ed9fc/greenlet-3.5.4-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07bd44616608d873d06735b63ef1a88191d6ca57c8d291d6559c71bc14c0893c", size = 632845, upload-time = "2026-07-22T12:43:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/79fd826f9ccaae0b84e1b4ef68dabba5e105bb044ffcd448a0b782fcba9a/greenlet-3.5.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d84d993f6e575c950d91a23c1345d18fe1a4310d447bf630849d7809196b52f0", size = 624002, upload-time = "2026-07-22T11:51:11.391Z" }, + { url = "https://files.pythonhosted.org/packages/42/e3/6086fa578ebb72772722cdc4bcd628459814b42e0c2db1e3cbd6552b3271/greenlet-3.5.4-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:3529a8a933582ad19e224792cac7372489526576b75b4c124e8e4f29948f4861", size = 435053, upload-time = "2026-07-22T12:39:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/27319f97e731298513dcba1a2e91b63e9d8811d9de22130f960b129b1bf1/greenlet-3.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58023945f421093de5e6fa108c0985a8659d43f49e0216da25099369a121bcbd", size = 1581533, upload-time = "2026-07-22T12:25:05.322Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6d/24240bf562e9786dd2799ee0a4a4dadb4ded22510f41b20245099159ac8c/greenlet-3.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bae2728e1897aa8df8cb1af38cd48b3a743aefe29372de7b8b7a9f532501e69f", size = 1645781, upload-time = "2026-07-22T11:51:14.805Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5a/442ab1a9ef7ca6bf7210e5397a95972206a91a31033a03c8900866a10039/greenlet-3.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:ca5726c0b08ca35ae873557266a78b2c3f3b2b7d7401aa5ff886c2045dd0111c", size = 247133, upload-time = "2026-07-22T11:39:20.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/9160210222386b1a378ff94db846b9508ca24a121cf684991561fdb69280/greenlet-3.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:7c1303791d603080cac6fc3b34df51c3b75b723739c282c8029e48a0d241672f", size = 245500, upload-time = "2026-07-22T11:40:22.185Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a7/6ab1d4f9cd548d15ab90da29947f2076100130bb179b0bde59f795a459e3/greenlet-3.5.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7e8afa5eac028f8140ceafe5ceec66e6aa127ddcb21452d2a564dcd2900b5f22", size = 295410, upload-time = "2026-07-22T11:40:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7a/422f63b4715cbc0b24385305407adf38b48f6bb68b3e6b04090e994d0f5a/greenlet-3.5.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73b37afe369021423ea53dd3123e04bffa7e93ac64429b9f50835b2e4fcae7cf", size = 661286, upload-time = "2026-07-22T12:26:43.8Z" }, + { url = "https://files.pythonhosted.org/packages/d0/31/5a1cac663bf5582190c5a714ef81364f03cde232227f39748f8ae4c11da5/greenlet-3.5.4-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ef964f56dfcb6f9bbef2a190d9126795eac408716aeae47b5e7c73c32aafca9", size = 673517, upload-time = "2026-07-22T12:29:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/9c/bf/250c2921c7b585dde12f5239e313ca2dcbc464d161ecca36e4e6ef21762d/greenlet-3.5.4-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cef589bc65fae02d10bca2ac341191c5b33acc2967892ebf4fcbd10eabb7a74c", size = 677968, upload-time = "2026-07-22T12:43:46.788Z" }, + { url = "https://files.pythonhosted.org/packages/15/4a/2a82a1e3f8aaca020853ac8d12211280ca2b231aa08ea39f636f1060c319/greenlet-3.5.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c53ff01a5c53a40f2c16820ebc56d7c61a77f5fbe009dadd96292d5682f80f8", size = 670917, upload-time = "2026-07-22T11:51:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/18/40/10bfcf6513558d82f7b95dd728001c63bd388259fe27d3e30ae01f103430/greenlet-3.5.4-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:dfc41ae893d9ceaf22c824f2153a88b30651b20e8758c2cd9ac143f23640563c", size = 480643, upload-time = "2026-07-22T12:39:54.149Z" }, + { url = "https://files.pythonhosted.org/packages/68/b0/e379a152b17bfdfa95795af4049e37c0fd1b4d81f020d426db104ed07c77/greenlet-3.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecca4d80d55a01ad6b23b33262662956149fbb7b2c6be2910f1705921958cbf3", size = 1628478, upload-time = "2026-07-22T12:25:06.678Z" }, + { url = "https://files.pythonhosted.org/packages/5e/43/bffdfa64f7317f954c5c1230b5dd5922676ce198689a68c1ac1ed4b1b1a5/greenlet-3.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffbc533e0eaf8e80d8471411646ab88fe58f641d508c0b02b24494479f4d9ec", size = 1692021, upload-time = "2026-07-22T11:51:17.008Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/f799f9637e2c6e9b0b716015e339040598b058cf7654dfc0d67468b177ed/greenlet-3.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:305f69e6c4523d7f6979ed001cff4e5853c063e5da04880296603aa0227e544c", size = 248031, upload-time = "2026-07-22T11:40:11.007Z" }, + { url = "https://files.pythonhosted.org/packages/05/75/625bcdd74d5e6b2dca1ecba3c3ac77bcf8a026c21a649a46cef23e421f97/greenlet-3.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:f260930bbbbcf9caee661211235a5111c86dfe5832fdf6ae4570da1e0995320f", size = 246892, upload-time = "2026-07-22T11:40:27.357Z" }, + { url = "https://files.pythonhosted.org/packages/ec/69/35c62ed49c320cb4d98e14698ccca5467d3bfe683984172be9cb564d9ce3/greenlet-3.5.4-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:41ddab54e4b238f4a6c323f39b4e59e176affd5a94d461a9fb7583dac74240a3", size = 305571, upload-time = "2026-07-22T11:40:31.659Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/64d60216b3640dcb0b62d913dd9e0d80030c09115bb2e4ba70c95d10ca45/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3dabe3e2809013052c68bdf0b7fa5f5f2859c43a80803131ad61af9cabd7867", size = 672568, upload-time = "2026-07-22T12:26:45.298Z" }, + { url = "https://files.pythonhosted.org/packages/5c/de/ba3ab0a96292e53039530333b0d2ae18d9e508f3a325cd7bf15f8172944c/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:27d3f00718634d4520a3a150154ac5da36f257869d41321953375b90bfbbc72c", size = 680076, upload-time = "2026-07-22T12:29:06.125Z" }, + { url = "https://files.pythonhosted.org/packages/ae/db/24a10af12bf8e639cec46c38b9ce1a282543ba42ff4fb0b31a970f1ab603/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39169a11d87a6a263afda3e9a27d1df16d0f919d40a4837cc73986c9884c0dd8", size = 681690, upload-time = "2026-07-22T12:43:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/aeada79083c6f1c15f45d77a332f9c441af263ee298e3eb17522cd337d22/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbd60b5763c6543c1827e48faaf14ea9bfbad245f52b1a4d76a2a2d8884c6c66", size = 676733, upload-time = "2026-07-22T11:51:16.027Z" }, + { url = "https://files.pythonhosted.org/packages/f4/60/44a2eca7b9fd71ae0fae7ff184da1cd3169d176652b97aa1cffcbb0ef961/greenlet-3.5.4-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:bd3d1145f603b2db19feb9078c2e6855eb7c67e15580c010ed815cee519b86fd", size = 510263, upload-time = "2026-07-22T12:39:55.678Z" }, + { url = "https://files.pythonhosted.org/packages/e9/10/2392fc3a98948652ef5fd1e7275c04f861dd13f74b78a2b4309f4ee4d090/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f00f910f0e7b35416c63b23ad78b769aeccfc1775f712b43c4ee525624a2eef7", size = 1637327, upload-time = "2026-07-22T12:25:07.879Z" }, + { url = "https://files.pythonhosted.org/packages/55/c6/e7237a3dfa1f205ed0d9ea1e46d70bd2811b32d516266399fb59d28ab90a/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:91c26423753b92caf41ab3f98fd547d7374d4d9fc2d85be041886c1579d9255e", size = 1697493, upload-time = "2026-07-22T11:51:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/55/e3/4ba8154ba2a3d43729e499f72471b4b5c993f3826d3e24da81d5f06d6572/greenlet-3.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:ee032b91fd8ec29ec6c4cea2b8c561b178435134bd0752c7334b94e9c736c132", size = 251637, upload-time = "2026-07-22T11:40:37.44Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/e3f96dfc100261a29545ddc8270cafe58f9195b6651466910e820910de77/greenlet-3.5.4-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:178111881dd7a6c946471fda85485ec796e1043c2b939f694b096e2ecf986809", size = 296076, upload-time = "2026-07-22T11:39:38.364Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3d/da52d208e5c977bce8667e784729e584e38b5785f4c1ec0f4c836e9a1c42/greenlet-3.5.4-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d92df08dd65fede97fc37aad36c2e9dcda3b31c467f8e0c2c096456cb818e927", size = 666870, upload-time = "2026-07-22T12:26:46.691Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8a/7e6dee25cb8a8cf9b362c8e597cc269593378bd916f16c736c059a52e85a/greenlet-3.5.4-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99e8f8c4ebc4fd80aa26c1280ae9ad43a0976e786349703a181cf0bae60413e5", size = 677678, upload-time = "2026-07-22T12:29:07.508Z" }, + { url = "https://files.pythonhosted.org/packages/51/a7/dafc7415d430b0a43a16396eb49ecb3b62fd720877fb259cc4dcfaf5f31e/greenlet-3.5.4-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f17e362d78e37559e0506c5a7d066bdd45073c36a0127a543e8a0df27242ff3", size = 681428, upload-time = "2026-07-22T12:43:49.623Z" }, + { url = "https://files.pythonhosted.org/packages/6c/21/5a38699fa45de749e3857d93b8f07e4c20489e77c2d35d915a2e1c456606/greenlet-3.5.4-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:394de08dad5ffcb1f50c2159d93e398d9d2da3ed437645eaa54771fa720db9f0", size = 676067, upload-time = "2026-07-22T11:51:18.163Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d9/6298f3432de301d4718766cf934bd73c418c73f81fbb77247319364b0d96/greenlet-3.5.4-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:cd320d998cbaa032932830448e39abf3c6a12901295e386e8114db926e10cffb", size = 487446, upload-time = "2026-07-22T12:39:57.044Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ba/863116ab8ff1ca7a729e327800268939d182db47aa433db70e216e7d9194/greenlet-3.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:c883d61f2282d72c767a14936641b3efcbde9d82f1080712aaea0b1d3126cb88", size = 1633489, upload-time = "2026-07-22T12:25:09.605Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7466ced82818d6132462d7f26b3f83c66ea15d2b193a6c0088d558ed7d95/greenlet-3.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2a924f15d17957e252a810acefcb5942f5ca712298e8b6fcaed9a307d357522c", size = 1696584, upload-time = "2026-07-22T11:51:21.304Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4d/55b638489260065de9ffce606c8b5d04507bef705de4b212a0c3d6a1a0df/greenlet-3.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ed17e5f3420360d5b459de8462efb52060399a5326a613d4cde31cef63ef95da", size = 248297, upload-time = "2026-07-22T11:42:04.055Z" }, + { url = "https://files.pythonhosted.org/packages/bb/08/9dd4ae635da93d41dc268bc34bd62a9d711ed8b8825c5d22ac910c7d6e6d/greenlet-3.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:f908898d6fa484ce4b6f447ce70ea99b52c503fee419e53cf74d60a16bc9e667", size = 247423, upload-time = "2026-07-22T11:44:00.764Z" }, + { url = "https://files.pythonhosted.org/packages/19/66/7c87ed9cdbf1d49c2c6cd1c7b9dd4d16c33b24235ca03972293a1876b30c/greenlet-3.5.4-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:1833637f17d5e7472548a48575c394fe39f1b1890d676d162d86593610f44d8c", size = 306487, upload-time = "2026-07-22T11:41:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/5b/05/0a4201e7c0054866eefc05da234f236dd4c950d0fbf9ca0517141f01b269/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12cda9122e03341f1cb6b8207a19d7a9d375e52f1b4e9243918375f40fd7b4b9", size = 676479, upload-time = "2026-07-22T12:26:48.129Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/4b6b8c5a3aec70f0649cd89662d120fdd6421e2bdc8e3b15c3ab5ec568d8/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d83ae0e32d14957ab7170785a20f582635c8474deab1bfbb552b17e769a6ce25", size = 684321, upload-time = "2026-07-22T12:29:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/88/15/0b167aeea95285b0e654ddce651922f666c089363c2ec528ca8b9a9ba74f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:123aa379c962ed5fe90a880327e0c3066124ac64ec99e12a238be9fd8eb3db3d", size = 685995, upload-time = "2026-07-22T12:43:50.993Z" }, + { url = "https://files.pythonhosted.org/packages/24/c9/b49c31c9a972eee91e260445770e922244a7efc542697f51a012ec046d0f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f1467de1bb767f75db0aa34c195e3a496d8d1278c796e70c24ce205d3e99cde", size = 681293, upload-time = "2026-07-22T11:51:20.43Z" }, + { url = "https://files.pythonhosted.org/packages/de/90/c023ec337f32ff505be7db759c80d98f0532bb94d0c6fa13645efe9bee2e/greenlet-3.5.4-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:adf2244d7f69409925a8f22ed22cc5f93cdfe5c9dc87ff3476be2c2aaae61a05", size = 516928, upload-time = "2026-07-22T12:39:58.359Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/7f2d4653770500eee667866016d42d7a68e3d3462f80df6b8e3fcd48a0eb/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0fa53040b78b578120eecdc0265e3f1051487cc425d11a2b7c761daadf4feaa8", size = 1642474, upload-time = "2026-07-22T12:25:10.819Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d8/8cba31036a4caae448087ba5d150660ab03b4a0f54d9150f6495a3be7262/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:60e0bc961d367df506660e9ac0177a76bc6d81305300704b0977d1634f76efe2", size = 1701012, upload-time = "2026-07-22T11:51:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cd/3f77a4cce3bae631b08eb52f53a82a976669600337e21dfdba811cb50267/greenlet-3.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f680e549edb3eaf21eea4e7fe101e15ec180c74b7879ab46adc080f22d4015d2", size = 251977, upload-time = "2026-07-22T11:41:38.125Z" }, + { url = "https://files.pythonhosted.org/packages/93/e8/65e8707d00fe2a49bf12f609a9b2b39ba6dd23c2810eacad877c4fc94bfe/greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994", size = 250538, upload-time = "2026-07-22T11:40:17.985Z" }, +] + +[[package]] +name = "gunicorn" +version = "26.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" }, +] + +[[package]] +name = "httplib2" +version = "0.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/f5/ccf58de92d61e3ad921119668f54ed36ca1d0cf5dcc5c1657dfb164fd78b/httplib2-0.32.0.tar.gz", hash = "sha256:48a0ef30a42db65d8f3399045e1d09ab0ba66e3b9efc360d07f80ea55d286025", size = 254283, upload-time = "2026-06-26T10:13:56.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/a0/550eec327e5f5c7b732531c489f5307efec41f047b0d703bd4ca1e5ad2db/httplib2-0.32.0-py3-none-any.whl", hash = "sha256:dc6705cacdf3fb0a2aba7629fa33c90fd93e30035db0c157325826be177e4816", size = 93148, upload-time = "2026-06-26T10:13:54.985Z" }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "isort" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, +] + +[[package]] +name = "kegbot" +version = "1.3.0" +source = { editable = "." } +dependencies = [ + { name = "addict" }, + { name = "coloredlogs" }, + { name = "dj-database-url" }, + { name = "dj-email-url" }, + { name = "django" }, + { name = "django-bootstrap-pagination" }, + { name = "django-cors-headers" }, + { name = "django-crispy-forms" }, + { name = "django-imagekit" }, + { name = "django-redis" }, + { name = "django-registration" }, + { name = "django-rq" }, + { name = "djangorestframework" }, + { name = "fakeredis" }, + { name = "freezegun" }, + { name = "future" }, + { name = "gevent" }, + { name = "gunicorn" }, + { name = "httplib2" }, + { name = "isodate" }, + { name = "mysqlclient" }, + { name = "oauthlib" }, + { name = "pilkit" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "psycopg2" }, + { name = "python-gflags" }, + { name = "pytz" }, + { name = "pyyaml" }, + { name = "redis" }, + { name = "requests" }, + { name = "requests-oauthlib" }, + { name = "rq" }, + { name = "whitenoise" }, +] + +[package.dev-dependencies] +dev = [ + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "pytest" }, + { name = "pytest-django" }, + { name = "requests-mock" }, + { name = "vcrpy" }, +] + +[package.metadata] +requires-dist = [ + { name = "addict" }, + { name = "coloredlogs" }, + { name = "dj-database-url" }, + { name = "dj-email-url" }, + { name = "django", specifier = "==3.2.15" }, + { name = "django-bootstrap-pagination" }, + { name = "django-cors-headers", specifier = ">=3.13.0,<4" }, + { name = "django-crispy-forms", specifier = "==1.14.0" }, + { name = "django-imagekit" }, + { name = "django-redis" }, + { name = "django-registration" }, + { name = "django-rq", specifier = ">=2.5.1,<3" }, + { name = "djangorestframework", specifier = ">=3.13.1,<4" }, + { name = "fakeredis" }, + { name = "freezegun", specifier = ">=1.2.1,<2" }, + { name = "future", specifier = ">=0.18.2,<0.19" }, + { name = "gevent" }, + { name = "gunicorn" }, + { name = "httplib2" }, + { name = "isodate" }, + { name = "mysqlclient" }, + { name = "oauthlib" }, + { name = "pilkit" }, + { name = "pillow" }, + { name = "protobuf", specifier = "==3.20.0" }, + { name = "psycopg2" }, + { name = "python-gflags" }, + { name = "pytz" }, + { name = "pyyaml" }, + { name = "redis" }, + { name = "requests" }, + { name = "requests-oauthlib" }, + { name = "rq", specifier = ">=1.10.1,<2" }, + { name = "whitenoise" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "pytest" }, + { name = "pytest-django" }, + { name = "requests-mock" }, + { name = "vcrpy" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "mysqlclient" +version = "2.2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/b0/9df076488cb2e536d40ce6dbd4273c1f20a386e31ffe6e7cb613902b3c2a/mysqlclient-2.2.8.tar.gz", hash = "sha256:8ed20c5615a915da451bb308c7d0306648a4fd9a2809ba95c992690006306199", size = 92287, upload-time = "2026-02-10T10:58:37.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/d0/22b896a2f8f797ee3517a0c1c1fe576cd38768cd25e2a7362a7277725ed4/mysqlclient-2.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:86db31bba7b3480fec2751350e9790e24f016f89af33a87bab7e79f7196474e8", size = 206873, upload-time = "2026-02-10T10:58:40.996Z" }, + { url = "https://files.pythonhosted.org/packages/44/f6/6253d116c024aeb11fb193ad14e2bae2bd3380690e885028abf798b1b0b4/mysqlclient-2.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60c9ed339dc09e3d5380cc2a9f42e86754fee25a661ced77a02df77990664c92", size = 206875, upload-time = "2026-02-10T10:58:42.223Z" }, + { url = "https://files.pythonhosted.org/packages/db/1f/7d6b3fbfc8a317805fdb8bbfbc23af99f2090497090e82513daf491a29ce/mysqlclient-2.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:000c7ec3d11e7c411db832e4cfcd7f05db47464326381f5d5ae991b4bb572f93", size = 207171, upload-time = "2026-02-10T10:58:39.827Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d5/76e369b0fdccd2eb9ed7d890e4e3e23aa1344fea62f0180d7f1574285e54/mysqlclient-2.2.8-cp313-cp313-win_amd64.whl", hash = "sha256:a81f5e12f8d05439709cb02fba97f9f76d1a6c528164f2260d8798fec969e300", size = 207158, upload-time = "2026-02-10T10:58:38.663Z" }, + { url = "https://files.pythonhosted.org/packages/42/44/6ae21b4e8490eb5167d04fa7452c462ffd72a502b96bf531477ef044a380/mysqlclient-2.2.8-cp314-cp314-win_amd64.whl", hash = "sha256:9bed7c8d3b629bdc09e17fb628d5b3b0a5fd1f12b09432b464b9126c727bedc0", size = 211644, upload-time = "2026-02-10T10:58:45.022Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/037d55623be9f681236e04abe12e1290847c06bd48270c3f19ac33493cbf/mysqlclient-2.2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:260cce0e81446c83bf0a389e0fae38d68547d9f8fc0833bc733014e10ce28a99", size = 213067, upload-time = "2026-02-10T10:58:43.389Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pilkit" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a5/bbe12d2c9dc06e29224c45a2cd7aa0ce923648588b6a15aadfee150bbd0c/pilkit-3.0.tar.gz", hash = "sha256:f6719e8cc0482e5447f5cb94f18b949d8e604ea9673a9b019c74d41b779e4eab", size = 402342, upload-time = "2023-09-27T22:47:18.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/ec/877b84b82cbcba6203e39d068cecfdbcfb61de69260cb851ea11be9b67f3/pilkit-3.0-py3-none-any.whl", hash = "sha256:fe1707b0411a1d0cbf9ad3986779fa5a346cec4582a188740924aa39f504d117", size = 20073, upload-time = "2023-09-27T22:47:16.285Z" }, +] + +[[package]] +name = "pillow" +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/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { 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/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { 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]] +name = "platformdirs" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "protobuf" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/3e/40c56d21154a1f3ababb69f675333d7fb70c8293f9ca42ea3e448327fc50/protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702", size = 216509, upload-time = "2022-04-01T18:26:17.992Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/33/ef7e1a21a4428e6e57820db4312a674c0dd1228be5ad57c0a776c425f9d1/protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4", size = 962269, upload-time = "2022-04-01T18:21:33.164Z" }, + { url = "https://files.pythonhosted.org/packages/75/b6/a49a33779d1309236aded36234bac5560d0f605f75114e4398b30a786735/protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e", size = 917927, upload-time = "2022-04-01T18:21:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/9a451d69492a695e2300643933590762aa4e5bbef2ae5f08f7d218ea6c3a/protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b", size = 1050440, upload-time = "2022-04-01T18:21:38.049Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e3/6ea01d64c981b81e0f65b68ea3d022aa870f3fd4be6845f4d02c1f64cb86/protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499", size = 779973, upload-time = "2022-04-01T18:21:40.421Z" }, + { url = "https://files.pythonhosted.org/packages/10/fe/280a6ffbe14a1ac458b741591de43a9ae212a3fe088812ad03eb9a4b10ae/protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab", size = 903844, upload-time = "2022-04-01T18:21:43.534Z" }, + { url = "https://files.pythonhosted.org/packages/ae/80/9eaa62a2afcc5407a6b7d2652c208f073df3a5c83b5bff90bf99553fbcf2/protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983", size = 162127, upload-time = "2022-04-01T18:26:16.304Z" }, +] + +[[package]] +name = "psycopg2" +version = "2.9.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/bc/f66df707ed1aec949fbf24e4460e4f4277a7ba23cdadb3965bb1f634ddb9/psycopg2-2.9.12.tar.gz", hash = "sha256:1dedb1c7a1d8552c4a6044c6b1c41a52e6a8e2d144af83eccac758076b1b7c15", size = 379683, upload-time = "2026-04-20T23:36:11.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/7e/940ade9113c6cdd8b4c0b27f820006bdf91c30170a5a95e8332104d5d3ed/psycopg2-2.9.12-cp310-cp310-win_amd64.whl", hash = "sha256:d5fbe092315fb007c03544704e6d1e678a6c0378139d01cea433dc59edf041b4", size = 2756868, upload-time = "2026-04-20T23:33:13.049Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/9cc069f8efc92383d3823ef8805c932f5fda723b3f07d2a8ee019ae2919f/psycopg2-2.9.12-cp311-cp311-win_amd64.whl", hash = "sha256:2532c0cdc6ad18c9c35cd935cc3159712e14f05276a6d29a6435c52d24b840c1", size = 2756862, upload-time = "2026-04-20T23:33:17.206Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b2/7319dc488444b1dfe9ce89c4440df1d5425e2579e6ce9a63e2b70f648491/psycopg2-2.9.12-cp312-cp312-win_amd64.whl", hash = "sha256:83d48e66e18c301d832e93c984a7bcbc0f4ac3bb79e2137e3bc335978c756dc0", size = 2757068, upload-time = "2026-04-20T23:33:20.664Z" }, + { url = "https://files.pythonhosted.org/packages/91/ab/03403779a251ca14a7f1b07a41c7aa735fd451f0aebe4c4f901a231167a4/psycopg2-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:3d23e684927d37b95cee9a943f6927b04ae2fdcd056fd0e2a30929ee89fee5a9", size = 2757176, upload-time = "2026-04-20T23:33:23.577Z" }, + { url = "https://files.pythonhosted.org/packages/73/4a/a3566f77501c21a6c2a1fc234dbe5dff74a86e3f150ef070e4ffb835e7f9/psycopg2-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:a73d5513bfe929c56555006c7a9cc7ae6e4276aa99dd2b1e2544eb8bb54f8b23", size = 2848588, upload-time = "2026-04-20T23:33:25.983Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/6d/f94028646d7bbe6d9d873c47ee7c246f2d29129d253f0d96cb6fcab70733/pyreadline3-3.5.6.tar.gz", hash = "sha256:61e53218b99656091ddb077df9e71f25850e72e030b6183b39c9b7e6e4f4a9bf", size = 100368, upload-time = "2026-05-14T17:55:04.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl", hash = "sha256:8449b734232e42a5dcd74048e39b60db2839a4c38cf3ae2bf7707d58b5389c0d", size = 85243, upload-time = "2026-05-14T17:55:03.262Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-django" +version = "4.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/2b/db9a193df89e5660137f5428063bcc2ced7ad790003b26974adf5c5ceb3b/pytest_django-4.12.0.tar.gz", hash = "sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758", size = 91156, upload-time = "2026-02-14T18:40:49.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a5/41d091f697c09609e7ef1d5d61925494e0454ebf51de7de05f0f0a728f1d/pytest_django-4.12.0-py3-none-any.whl", hash = "sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85", size = 26123, upload-time = "2026-02-14T18:40:47.381Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-gflags" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/ec/e31302d355bcb9d207d9b858adc1ecc4a6d8c855730c8ba4ddbdd3f8eb8d/python-gflags-3.1.2.tar.gz", hash = "sha256:40ae131e899ef68e9e14aa53ca063839c34f6a168afe622217b5b875492a1ee2", size = 52093, upload-time = "2017-10-27T13:04:08.546Z" } + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, + { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pytz" +version = "2026.3.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "redis" +version = "8.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/99/604f0b666d4c616d891cf77ebb9db6bb21601344c051aebf1b72b9ff915f/redis-8.1.0.tar.gz", hash = "sha256:6e1a19beef9225c83efd689c7e6b7da2d5215b1f42cd13b7fc3714d0a09c7b25", size = 5254356, upload-time = "2026-07-30T08:51:00.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/9d/c5731f6e3608663d4d3656fd8d3aecee8b509c3082818f5a13eae925baea/redis-8.1.0-py3-none-any.whl", hash = "sha256:a4fe1aac3d3b3cc791d4b3d5931c5a956045dc951ee74d1c913ee3ac4d2ee9fb", size = 560618, upload-time = "2026-07-30T08:50:58.497Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-mock" +version = "1.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/32/587625f91f9a0a3d84688bf9cfc4b2480a7e8ec327cefd0ff2ac891fd2cf/requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401", size = 60901, upload-time = "2024-03-29T03:54:29.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/ec/889fbc557727da0c34a33850950310240f2040f3b1955175fdb2b36a8910/requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", size = 27695, upload-time = "2024-03-29T03:54:27.64Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "rq" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/1d/a020e8f3559293635dd6283d037bfc15f866136498da3db1e5416c65773d/rq-1.16.2.tar.gz", hash = "sha256:5c5b9ad5fbaf792b8fada25cc7627f4d206a9a4455aced371d4f501cc3f13b34", size = 629881, upload-time = "2024-05-01T07:13:14.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/35/db396caf7cfe71a9e661cea520b276f1472df25015e7cb9143c65a1dca6d/rq-1.16.2-py3-none-any.whl", hash = "sha256:52e619f6cb469b00e04da74305045d244b75fecb2ecaa4f26422add57d3c5f09", size = 90911, upload-time = "2024-05-01T07:13:11.158Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, +] + +[[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 = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "vcrpy" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/d5/8a1f8eb603e2d35fbb0ecd1e309d0c5c18a0ecfc8c0a8f04088bbc8f833b/vcrpy-8.3.0.tar.gz", hash = "sha256:46d64e77e8d95e5c76c7d9a94ff05d8b38b2ae4e1d4869eb0235024b6fcb5212", size = 96117, upload-time = "2026-07-04T14:27:01.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/77/cb4219be91508399cbcb6143bad89462cfb16f6c638458f454a5d46ac95a/vcrpy-8.3.0-py3-none-any.whl", hash = "sha256:bd66e6143746778157f00e2a922527a8d96b2fdc350be8988a45a29c843815b9", size = 46530, upload-time = "2026-07-04T14:27:00.546Z" }, +] + +[[package]] +name = "whitenoise" +version = "6.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/2a/55b3f3a4ec326cd077c1c3defeee656b9298372a69229134d930151acd01/whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", size = 26841, upload-time = "2026-02-27T00:05:42.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/eb/d5583a11486211f3ebd4b385545ae787f32363d453c19fffd81106c9c138/whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2", size = 20302, upload-time = "2026-02-27T00:05:40.086Z" }, +] + +[[package]] +name = "wrapt" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/b0/c1f5a970721f06b85c0cd5142e0ff8fe067708abd779b0c4f4be7d61d09f/wrapt-2.3.0.tar.gz", hash = "sha256:681a2d0eefd721998f90642762b8e75c2159ec531b20ad5e437245ea7b06a107", size = 131509, upload-time = "2026-07-28T06:06:14.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/31/5822ce37ca8820c2ed35a498c67c8b37960b9cee2ba437fd32849d0a234c/wrapt-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0bb2797048db0956348cb3058c33bc4184614f13231389cfbccc16a5d32780a7", size = 81191, upload-time = "2026-07-28T06:04:04.858Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5a/3c6117938be98754578ab83f5a40d7d0ea2cd2c487dc5cd6027ee7228229/wrapt-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce9f398f868d2b3b27aa2ea4de79645ef9077aeeac8dfc2814b0d542c6a2b87f", size = 82255, upload-time = "2026-07-28T06:04:07.151Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0f/94ae724c5087eb6054c0d63febd7094947dcf302fe058e2e0488102a872b/wrapt-2.3.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad71df7a04dd3497e9302e81f4a7c91bd401ea0e15a9df9029527900f94bee43", size = 155228, upload-time = "2026-07-28T06:04:08.272Z" }, + { url = "https://files.pythonhosted.org/packages/6c/21/1f780bba935dcf697c0c59de9be3a559bbb8e31a53ca3f25422023738432/wrapt-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc82c2ccc8e234c844f5303d9f2984b346dcdd53e94823ce8420d2c75b4b9023", size = 157073, upload-time = "2026-07-28T06:04:09.459Z" }, + { url = "https://files.pythonhosted.org/packages/73/31/6c7799d7b6431fcd7e1b83245fb45258a2d2c3a2187fbaecb83572a72d7a/wrapt-2.3.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6e19531ae33c508cea7d84a7edfda01fa86e51b8d1a93a77712c55e6e469152", size = 151594, upload-time = "2026-07-28T06:04:10.784Z" }, + { url = "https://files.pythonhosted.org/packages/ce/17/42d670dbfafd49076c6eb2b7d67633d7e1c968e39bfb11a135acb6fac67b/wrapt-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df4ce31150bcd5d9f36f816aac3010ab4f4bf8672ac1d3b0ac7d539ec61c7c02", size = 156069, upload-time = "2026-07-28T06:04:12.316Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d6/c66b4ba4eda49257c84d5c2df26118280f09ca7905aee20d0064db778d13/wrapt-2.3.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e2e692bc0d63f881cf7006730a56bd4e0c2fab5dc318466942805d692b166276", size = 150930, upload-time = "2026-07-28T06:04:13.482Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f2/1a3b949c0322fb27396eafd1044328c1cb0400e0b32105d75a3cd03096e7/wrapt-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c8388ba7faf5dbf9ee106bb70d66f257629b1bd98091123e19e8a4553a319199", size = 154525, upload-time = "2026-07-28T06:04:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/147563a3dfa6e830c857b93b530ebd8c0cd9d540e5914aec8f9b12880c02/wrapt-2.3.0-cp310-cp310-win32.whl", hash = "sha256:e045ff75d7d94900fc32896ed93c45ce2d2cac28c9dead582ff9a5a49d446e35", size = 77879, upload-time = "2026-07-28T06:04:16.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/eb/921405b4dc55d4f8be4c700ef120539fdd75d5fdb50d83bd257171ee18e0/wrapt-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4fc96b159af0a3e0faa72475a69d66292bea72a5bed1e1aca1bffbddc3cb2b0", size = 80733, upload-time = "2026-07-28T06:04:17.43Z" }, + { url = "https://files.pythonhosted.org/packages/b6/13/75947450c5bb57795fa86384721cd52c5c4deb0879022f309501a8a85d44/wrapt-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:1236fa25173ca964c97422470482e9011b9e3c7ed0d75798b40b3da3b0e0e760", size = 80199, upload-time = "2026-07-28T06:04:18.761Z" }, + { url = "https://files.pythonhosted.org/packages/00/b8/9182e4c618a847be0baccb68e4602b070d0fa22c782cf058f4bc66b32709/wrapt-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ab559e1b2551d23d54db2a0001c6d73bad022a254639561c5f6c382a9d6c2fe", size = 81427, upload-time = "2026-07-28T06:04:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/613cefd9c5977366b1587e61c0b428176d382e6d75b454084c5e58503042/wrapt-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bff9a671bc00709cab5a7f745c592b5671873449db0ee2a569af994f16b29a4d", size = 82360, upload-time = "2026-07-28T06:04:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/71/71/4cd2151a236f44a6e2dd4ed8011838d7ba0be3d656c8bafdfc65a2ed1917/wrapt-2.3.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc648a335d7e01adb3640b25f02fd0ea05886cf04d0af7f4ee902bc7b5e466e8", size = 161700, upload-time = "2026-07-28T06:04:22.723Z" }, + { url = "https://files.pythonhosted.org/packages/49/2c/bc508fee75eb2919ed69769800b09968e4aab16897f909a23f39c81e323f/wrapt-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0077f3d65541925fa83002f967b22ad6550d24813ac64cb905f717194128d9c", size = 162922, upload-time = "2026-07-28T06:04:24.177Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e5/04f34d38e66d857dfc2fc4088d60e70c0e422467822defa49b2b4a26e17b/wrapt-2.3.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9790ea25190a4e0fe4cdf4eeb868e9d75f8a024a70a5b6bf9c348a3a2b72e731", size = 156125, upload-time = "2026-07-28T06:04:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/c35940ea1c423f129ebe4361db853bc80d4def6326242e1206fa15bf94f4/wrapt-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:816877aa749253149f9ecfd2635d4d948ecfa338e1a0311d187b1acb1bb8a3eb", size = 162039, upload-time = "2026-07-28T06:04:27.154Z" }, + { url = "https://files.pythonhosted.org/packages/0e/60/9bda34c3d7d182aa703fe35339ae0ed4c4dad5e5c587f93890143e1f87fb/wrapt-2.3.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d1c2c1b808600d2ea808e6360910a60ed5f409a4011655e10f9164ba0a414a6", size = 155110, upload-time = "2026-07-28T06:04:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ba/60bfd9b1a751f4fcb2d603668fc272d651ccdd339a56acf8c40ad21a0293/wrapt-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5ba1e5e08ddc46130e9682b2c249f2d1dd39bda9106ed4bd401b7519f18f41bd", size = 161089, upload-time = "2026-07-28T06:04:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/0f/32/2bd358c6f4f1305c813479d1e9ba746bebdd794f4a20107ab2b3ee0cbd45/wrapt-2.3.0-cp311-cp311-win32.whl", hash = "sha256:45c9279b373d15649dfa2c2077cb3408ea1a6d3125afbdab9d6b809a66f68e14", size = 78030, upload-time = "2026-07-28T06:04:31.241Z" }, + { url = "https://files.pythonhosted.org/packages/4a/62/ecc969b13b141fef89b888c9760821cb01a86ac8fc953911592c8e1e1522/wrapt-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:195b1842b4122fb54e3cd3dd5b2b4aa49302a5a61da901df0481f5c97aedde84", size = 80944, upload-time = "2026-07-28T06:04:32.655Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3d/9278ada8a2b3f24372b630361e84e9a7de7abc3784634860c26d1c37785a/wrapt-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:6db604ef0c67bdb2042ecdfd7b7f037cf09733557ca42360d1018285634f7b98", size = 80074, upload-time = "2026-07-28T06:04:33.811Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4a/d17a0fad1bf1c5f2c887ff71fef75654141b0880bff71d157d955b5bec3a/wrapt-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a45ffae742ce91a16e11cb6c7cd71e7f9994f3cbd283b962ab093f5c6dcf525", size = 82139, upload-time = "2026-07-28T06:04:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/51b92daaf6defb57f4dc56bdcce985400f75c6984a03ca5e78ccac717028/wrapt-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69e477046f2237ef0bc6547544ee73008dc764ca26eff44f09e976d221b34d5d", size = 82723, upload-time = "2026-07-28T06:04:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/28/7f/cfd9bc4b1f5e424eeea83d0493e43f3b1b02707ce8e50c47945873982bd5/wrapt-2.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d221a6e6ddd302b8397433184e96b59f259f50024b854db1c411a881586b6b8", size = 172381, upload-time = "2026-07-28T06:04:37.674Z" }, + { url = "https://files.pythonhosted.org/packages/cb/89/ff7814f6eb6856b479946117d1138a2fbb46cdb6b1f379db359056c69743/wrapt-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:392158c9a7f2ab1b8699418bfc0fe6f83548788c418b27d7bf2019ad3405cebb", size = 174120, upload-time = "2026-07-28T06:04:38.987Z" }, + { url = "https://files.pythonhosted.org/packages/12/1e/8eded8615d39e3ce81f626937a3a87b280a2a86239a2bf14a4b4bb345034/wrapt-2.3.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e5301c35cf75655eb33498f2bd6ae8703ca19940e3167dc9cdf740c712a39c60", size = 163035, upload-time = "2026-07-28T06:04:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/35/ea/a0af2d9da62897af2a055484920de05dade30d2ba2c0d65cbdea875d3d8b/wrapt-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:418f54bb09d1762db02c7009b4051149893af3153a87f92d70356703c11eea02", size = 171887, upload-time = "2026-07-28T06:04:41.614Z" }, + { url = "https://files.pythonhosted.org/packages/7e/dd/63cd4c864c65ef4906df64bd2d378f4a62b54f28063f282dfb3bf93caead/wrapt-2.3.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1598becd30f8f2777d18564064eb4f4dbe1ab0e05a8f09786d0ef505ac782bf3", size = 161113, upload-time = "2026-07-28T06:04:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ee/82f1fc9e431b5c2c5a6d201aa865dbeae3984c311c6d11a185f0c8367cf6/wrapt-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3da470536bf9645143323dd41b32db55c6f4304ad382094c1a1da8a92061e10d", size = 170530, upload-time = "2026-07-28T06:04:44.212Z" }, + { url = "https://files.pythonhosted.org/packages/37/a5/5dc590e863a419930d988f8b7ca3e75a6befcfb10b6003b3a152f3d5f732/wrapt-2.3.0-cp312-cp312-win32.whl", hash = "sha256:fb8e2e6704a1e0b1b989546c69e2688371ef4a07fa5f61bde3eb6211186f5ac1", size = 78323, upload-time = "2026-07-28T06:04:45.484Z" }, + { url = "https://files.pythonhosted.org/packages/51/f9/4a6925a07951df56394f7e6ebe14f69f1c5ef9d87aa63e0839acf15aa63a/wrapt-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdc021cb0b62471d6aac7f2bd92f3b4658073775f9ee7fcd325c511129e7bcc8", size = 81180, upload-time = "2026-07-28T06:04:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4f/8b5de0395b2a72216751d41c9861df6facaeb611b619d8810ed2b3b23eb2/wrapt-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:67bfe2485f50368c3fcd2275fc1fd100e350d601e0058921a7c82678a465aeab", size = 80155, upload-time = "2026-07-28T06:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6e/0f88a072483e76b881e3fdcd6b6ffb4a5791002514fe541e72b1b73c859a/wrapt-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d3fb71e65b001adfc42684522eeccd9c21d8ba679945abc993439567b66e59f", size = 81960, upload-time = "2026-07-28T06:04:49.622Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ff/b7e2776e7c294075eb712cc9ef573d1b818f393006d09787262b8fc871c4/wrapt-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51a7a4181c1295774812271fbcd7c909df372bc25579d4ed9eb875caaf0ae86f", size = 82435, upload-time = "2026-07-28T06:04:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/d8/90/343bb5d0f1f9669bc252a6073f085b4abf862511bd5c9c9eaec754341f1d/wrapt-2.3.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9045917809c63fdf7abe3a2ceaed3d670b8ee4500ddd9291192d30aeb34467c5", size = 170350, upload-time = "2026-07-28T06:04:52.187Z" }, + { url = "https://files.pythonhosted.org/packages/59/f8/13b79a392930bd0dd6b86cbfbfe1c40944110456e1dc6d809e5c46ece904/wrapt-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54ca1d5573f69b5fe1d74f1f65799c68015e82f685efec9fd8cfa40a094c44d0", size = 170022, upload-time = "2026-07-28T06:04:53.599Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/4f1b6918f5290db959d6e0c07f77385d87cede29c39c9cf8f145e9c82954/wrapt-2.3.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:242b60c21e30866e6a2fa606c612b47c553fa60c0eaeeeb7797fb842ac0ce609", size = 161043, upload-time = "2026-07-28T06:04:54.936Z" }, + { url = "https://files.pythonhosted.org/packages/01/e1/45d3cf74414780bdff6d0380467e003f6eb0f028b6c9403db868dbc7209c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3f3d7ec0a51fbfe00d3aef047641ff2c58b25565b4717fc1f90e050be01cba8", size = 168576, upload-time = "2026-07-28T06:04:56.261Z" }, + { url = "https://files.pythonhosted.org/packages/f3/73/2fa58dd97f191c997755e2c6d569a68f0c433db4e4b36099bdd7227b6cac/wrapt-2.3.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:261f53870cd4fb2bf38f9f972c56c728fd224cb7c65721307de59d9e7e6741ae", size = 159140, upload-time = "2026-07-28T06:04:57.754Z" }, + { url = "https://files.pythonhosted.org/packages/29/a8/08a56e2000a8816d449dcbad8c8b081697acbbd490821ceca0f9d8e8d20c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8159ec0b0cb7608175eb150de94c19e34f4d47ac655f5ca9baf45df6b688ffd3", size = 169263, upload-time = "2026-07-28T06:04:59.161Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d4/354e1725e35a73b2af4fa70a3e024c7a5d1bf1802dfb862dcb668aae0253/wrapt-2.3.0-cp313-cp313-win32.whl", hash = "sha256:10461884b3014fbfc8eb7d09a93c5f246363e6711d9d881f95eb8c27fdef049f", size = 78241, upload-time = "2026-07-28T06:05:00.507Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7e/34c87fa2174848dfee820322aaa318bab08913998ccecc8d2f57b4ad4639/wrapt-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac870cc97b73bb00ac353329e9559a4bebc47c4c86792ed9b23b58c15b6ad838", size = 81113, upload-time = "2026-07-28T06:05:01.839Z" }, + { url = "https://files.pythonhosted.org/packages/11/86/fcc9a530579e008c9478bb565a6cdfbfd33536660f069c8b91a6607c5050/wrapt-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:a65e8db2b4e90c2e7ade931086351c98ef420bf7a94ee08c95ac8a3cbbc43579", size = 80182, upload-time = "2026-07-28T06:05:03.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/50/3864848b95b28ef73e17551fc8dccbff2628a834f52cf26a57f9c419fb83/wrapt-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fd1f2f557dd3491fe75905e578f4db967393d40d1a8f468edc4d40ac7f2d5944", size = 83921, upload-time = "2026-07-28T06:05:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4c/3d1921a60c3e8c71c540ff136e6a47a1fbccf7f671e818394889f7871d9c/wrapt-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9f5d2aec29dfc76c37e23897dee92766a3fd4f3bff3ae7fc9c6b4bf37d8c1360", size = 84412, upload-time = "2026-07-28T06:05:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/fa/1a/4a796ff7adb26ada6d4b758c94d47a38320b085e7099afc088efbbcdb006/wrapt-2.3.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:646d20d413ffcd1b0a2f700076e2d0252d872dcb7754860a73e45a59ea883614", size = 207168, upload-time = "2026-07-28T06:05:07.256Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3e/d7777776806c579b761bac2f91721dda9f04c7a1b380213c5935cc750ae6/wrapt-2.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:379f670f45b7bb8993edd9f6fc36c6cc65edb81cffa0b504be34acb0303fff0a", size = 214351, upload-time = "2026-07-28T06:05:08.945Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/2d64d394df7bf181955b3bb562bf33c4492fb4be113f53071106d43ad8b5/wrapt-2.3.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6208f302f110295d64b22a7ac96500c791bf492dce4366e622e4912b077c9687", size = 199020, upload-time = "2026-07-28T06:05:10.418Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3d/fb31d3db7d9834d265fb1a27a2adf0ddf51557c67458c97b22439ad6ae3d/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ed635a9ca4f3a5a2b900c10c69e823373bc00ebc114b459383596d3487da3570", size = 209969, upload-time = "2026-07-28T06:05:11.983Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d1/8724b5da582e62070dc9bf4d8bf1972f317297eefd7ba1f2b5c6393ccf6c/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e3b9eaa742ae7a0aaaaad4ca4b69469d757af2d6e6663ef1dadc47adec0aeb41", size = 196324, upload-time = "2026-07-28T06:05:13.557Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/3d9ef411149543016ee6bcf3af707f787cebd946527452b94bf122e9b7b4/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0f7284f88f4833705132d06d3b425a43095c2cbd07c58166aac3ab646ba12a4", size = 202610, upload-time = "2026-07-28T06:05:15.048Z" }, + { url = "https://files.pythonhosted.org/packages/13/9b/4fc042ceb757866dd4a5fc057b3b736f2b360d3703ce9f830d83dc9226e0/wrapt-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:7ebb274aba688b043429eb1500ff8a76ce0cb8ac0812ca3e301f06247b8722b3", size = 79178, upload-time = "2026-07-28T06:05:16.469Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/b94878f8eed809ca042685276bcea9f24e8c2ca7c9653bb80bbb920a68a5/wrapt-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c4bded758ad6f03b965830944a2f0bc5b2eb3767fe5a7310134315d1a6610e98", size = 82634, upload-time = "2026-07-28T06:05:18.026Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/663e1de5332a71685a729754312d327d4cada767c36e1c5a2db4c8de49e6/wrapt-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:d2cc64539da63e39ffb9c7ede849b6e8ddaaf7b3876b5cfb04efd85a5f3f4eb6", size = 81387, upload-time = "2026-07-28T06:05:19.417Z" }, + { url = "https://files.pythonhosted.org/packages/58/10/b073beaea89bc0d3670a75ff51139430a54b6af7ba7796507730634536dd/wrapt-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea52a0d0f08c584943d5764be0e84efa912c8da23c23e1e285ff2f5641c18fcc", size = 81978, upload-time = "2026-07-28T06:05:21.133Z" }, + { url = "https://files.pythonhosted.org/packages/b3/31/0916d9cebf848ed3f1a0c1888faee421747df77331e4db2bc527a9a85988/wrapt-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd85b0aa88efdb189d6ae2f35f4526943a8f091c38599c9c31478241c819e6a1", size = 82518, upload-time = "2026-07-28T06:05:22.562Z" }, + { url = "https://files.pythonhosted.org/packages/f5/73/31c1bf0f3384062751c2094dadb314916d70aa9b6bfd26d994b4a7b393fa/wrapt-2.3.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:141ed6211286a9660d8d6702de598b43f0934b4f0eda16393f100a80f501d945", size = 170187, upload-time = "2026-07-28T06:05:23.904Z" }, + { url = "https://files.pythonhosted.org/packages/ed/25/fce087d54b79b8905f3c3c9dd5f454bbd8d8acb80b960c4a6aee5b4659b3/wrapt-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e49885a62ec4ee854d1b9e6371fda6afd219917225752abf729a3f36d4df9a5", size = 169288, upload-time = "2026-07-28T06:05:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/0d09e6dddc6b7a7230ac77f50254b5980ab4fcd22976f72f8cc8a0404458/wrapt-2.3.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d6159c9b2fefec02314e1332dbbbfaf960e369dfd26bcf7f8b258b5732065b3", size = 160932, upload-time = "2026-07-28T06:05:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ca/0913af0d2ec0c43865d32d615f518fea66c13c5c930e489e9b0de248e9a8/wrapt-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24da48596326ef8e448cfa837b454f638713d3531262375f00e5a9681682fc07", size = 169017, upload-time = "2026-07-28T06:05:28.501Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f2/3d1e47ea81b822210f5df1bf942fd90780a75c055243d569b664529dea88/wrapt-2.3.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cd3a2edf0427013736b8127955cec62608c56e53ea47e82812ea32059cda407f", size = 159065, upload-time = "2026-07-28T06:05:30.01Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/ef2066ced8e5fca204e2b361e9708e36555b40949c583d997ea3b590817d/wrapt-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0df3bff4e7ce45759f33fd39335fe2f60477bb9ecf7b8aa41e7d07ee36a23", size = 168821, upload-time = "2026-07-28T06:05:31.649Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/016104650d4e572fa91506eb396b3dd8efbccc9284fdc1c9479c3d21db28/wrapt-2.3.0-cp314-cp314-win32.whl", hash = "sha256:2935d5454b3f179a29b12cf390ee47246740ba2c3a7545b1b46ba31a5f2a4a0b", size = 78700, upload-time = "2026-07-28T06:05:33.391Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/6fdc20a9f2ca304748b3f0819cbf377d55260562777bf0b615431bc3c181/wrapt-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:cc2cea812e5cb179a796b766747e7d3b21088760d8deb95676d482b8c8e6fa7d", size = 81422, upload-time = "2026-07-28T06:05:34.774Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a4/9cbd53bf05746bea2c392af39cb052427a8ec95cbd494d930733d8f44681/wrapt-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:22cc5c0a717bd4da87018ae0bffd4c19c6fb679d3ff357216ba566ab26c76cab", size = 80639, upload-time = "2026-07-28T06:05:36.228Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/6c5e4a0f66ea0d2b2dd267e8dd05a0014eea56840b3c8595d40b0a5d1f91/wrapt-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a6b5984cd65dd639546f0eb4b8eacf1c31cb2fe9fb5c27bffe240987cdb2cf84", size = 84030, upload-time = "2026-07-28T06:05:37.714Z" }, + { url = "https://files.pythonhosted.org/packages/6a/eb/a1aedf03283bc9cbf8a1783995ddc54e3c5a86878f19002d2c428494f4c5/wrapt-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c88abcf53daef80e01a75c7530e727fa6e2c1888fe83e3dcdba4c96216a1f5c7", size = 84419, upload-time = "2026-07-28T06:05:39.131Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/50d511c0dc5105563849e86daa3e16ac7feef699f79fb05af45ea70107d5/wrapt-2.3.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:85de890ff968196e92dd1ae73a9fb8970495e7650a457b1c9ef0ac3dd550bce2", size = 207171, upload-time = "2026-07-28T06:05:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/3f/59/9b538cf7795217e810699d16bc88b96a830d9b5c403eb2ec2db6b5f2ae81/wrapt-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50f416b74d092bb9f41b424e90dd457f365f7ba4b11de62a23679769a21bd85c", size = 214329, upload-time = "2026-07-28T06:05:42.287Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/9935d62b1499e5c8b3d191e99ba4eb31ca237a0b699142011a837e9dc7ea/wrapt-2.3.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39febbee6d77301d31da6996b152ce52452da7c7ef72aba10c2fa976dff9c295", size = 199079, upload-time = "2026-07-28T06:05:43.958Z" }, + { url = "https://files.pythonhosted.org/packages/2b/01/4446b80fa2ffa47a3449b250d004ba1c1937f07f64a179608fec735df866/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:93513bec052c6cd987f9f580c3df068c8bc4ebae6543736be3ca7ec5959cafcd", size = 209992, upload-time = "2026-07-28T06:05:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/d4/07/56f26c9f9979586a021e8148747004aba4498f49458c90b0502969b904e1/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:729126e667da34d251b8ebf8a45ef0c5ddadc21542b3d6e1abf4259ece6508df", size = 196334, upload-time = "2026-07-28T06:05:47.608Z" }, + { url = "https://files.pythonhosted.org/packages/8b/41/6d7bcc895b0f28b2250e10908f060687b9165429dcd7f22ddb3d4c031b74/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:626b69db2021aa01671ec7bbc9740e558522bd44c18cf2ce69bf3d666a014109", size = 202644, upload-time = "2026-07-28T06:05:49.183Z" }, + { url = "https://files.pythonhosted.org/packages/cd/25/7860927edba06b758b8852a6f02e832be715563c67a6795d94350bc81099/wrapt-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:629d73378082c00a8173031f9fb30a3ac6abbc894a5bfdfae71fabc60642d501", size = 79685, upload-time = "2026-07-28T06:05:50.976Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0f/270bafe92fde3b069a39bc01e39ee79340895b335640df861d43d2a51885/wrapt-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:42869085687f0aefd57c0f636c3f9354f8ffb321a8ba9cb52d19beb796e561c5", size = 83104, upload-time = "2026-07-28T06:05:52.405Z" }, + { url = "https://files.pythonhosted.org/packages/55/b3/af176d79a8515a8a720eccdad9a96f6e31a30abf2865430c8c42adf2fd13/wrapt-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b1e5aa486e269b00ed35e64771c7d0ab8096cfd2643405ca8cd60ebedc099a51", size = 81774, upload-time = "2026-07-28T06:05:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/00/39/3daf9f47be208606586de4568ba6713db53ebc8fd7a575aea1fe57983b69/wrapt-2.3.0-py3-none-any.whl", hash = "sha256:d8c7ed08477429752b8c44991f40ad7838b18332a160698740a6bfbc10d998a2", size = 61866, upload-time = "2026-07-28T06:06:12.9Z" }, +] + +[[package]] +name = "zope-event" +version = "6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/41/faa10af34d48d9cd6fa0249a1162943ad84a9590bd1a06939981e6640416/zope_event-6.2.tar.gz", hash = "sha256:b97d5d6327067ee6b9dfcbdf606ade9ade70991e19c162e808ea39e5fcf0f8d3", size = 18958, upload-time = "2026-04-28T06:24:10.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/33/848922889e946d4befc415c219fe516af75c49555d8e736e183bfd30db42/zope_event-6.2-py3-none-any.whl", hash = "sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874", size = 6525, upload-time = "2026-04-28T06:24:09.176Z" }, +] + +[[package]] +name = "zope-interface" +version = "8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/dc/50550cfcbb2ea3cbca5f1d7ed05c8aa840f831a0f2d63aec0a953f7c590e/zope_interface-8.5.tar.gz", hash = "sha256:7a3ba1c5877f0f3e3906b02ddf793abed2becc2948116414ce0e1dd820b68d6d", size = 257957, upload-time = "2026-05-26T06:50:14.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/43/9cd98bee951d23848de690ba2809f87e3b22c67c370987acc960da15ad37/zope_interface-8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0c8aa2bf8f3911ef37b87deb1bbe225a310e6eb6522a16d77f5d8330c4f6fbe", size = 210951, upload-time = "2026-05-26T06:49:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/8f1a29966bcf863e3a2121edcafb81c55715de7886bcc9544749cc79e7da/zope_interface-8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:efe234a0fafb4b6b1602e9be9245b97c2bf06d67c07af5a4bc3c0438978b555c", size = 211309, upload-time = "2026-05-26T06:49:02.732Z" }, + { url = "https://files.pythonhosted.org/packages/9f/9f/37e564eaaf85e3abc1ada40a79fa43f2ab45bdb67431b0ec0fe29e4763e2/zope_interface-8.5-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:dabeb6fe1228d411994f300811edc6866fff0cdcbc9cef98a78f05ea0da42e37", size = 254881, upload-time = "2026-05-26T06:49:04.303Z" }, + { url = "https://files.pythonhosted.org/packages/06/61/e6501d8ea7a2cac3217e03f404e1f98c1df7191d83cfe86b1895fbba5dac/zope_interface-8.5-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:147a9442dcc2b7339ecdb1be2b3cdb098e90462e39425054053ebfb50d99125a", size = 259811, upload-time = "2026-05-26T06:49:06.373Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/bfa25ef480b02af6e9452c478483fec75e87c9e2b60c407fd0b1f6054b9c/zope_interface-8.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a17e681224267880707c9ec9e730ad9a1ad2d65c371256843efba6cf48711b58", size = 260358, upload-time = "2026-05-26T06:49:08.317Z" }, + { url = "https://files.pythonhosted.org/packages/64/51/2b518072fea76242da64451d501c69b7b5ccdef9b57fead584ccf1c180d5/zope_interface-8.5-cp310-cp310-win_amd64.whl", hash = "sha256:d178968a1a611df30549a717d1624cb38ca810347339e3e37b7baa6f6781a170", size = 214822, upload-time = "2026-05-26T06:49:10.441Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/83ad110fb847413affe71609bb50e59e1aa082e1236030122227c7c283d3/zope_interface-8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:afc66ccaef2a3c0bef6ca02aad40d29a39276389dad16a8eac36f9f385e4d057", size = 211426, upload-time = "2026-05-26T06:49:12.595Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a7/6b6e0c31ac240cb9fc015ae9ed45ca54be886c18fcf7bfa2377a4d7a8785/zope_interface-8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28044972187245d7a309e4699319bfdbd2ffcbf7176d1d4ddf5adffb2dea80f", size = 211850, upload-time = "2026-05-26T06:49:14.474Z" }, + { url = "https://files.pythonhosted.org/packages/37/36/7599ecabcf80ce4fef2e1ef3c5ac0d4696b61f03f724cc44022f4d226af9/zope_interface-8.5-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:03bbecc7982af713d7499d4084bc03916413d17ffd45f89009348cc0c1d9e376", size = 260711, upload-time = "2026-05-26T06:49:16.568Z" }, + { url = "https://files.pythonhosted.org/packages/03/3e/1774b0ee46ccbb5498ee3c33ece40315b6ef58bc71957be94bd345340bc1/zope_interface-8.5-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf917009a4a7457c7290225a019f4a0aa706d96accd2cfdba2418d3bc1fcde2f", size = 265277, upload-time = "2026-05-26T06:49:18.656Z" }, + { url = "https://files.pythonhosted.org/packages/b6/09/e533b2ffabaae4e5d5730d6768a591cf335defe8e37bec2ad905d09be656/zope_interface-8.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:31cff25b2aaedb5267e6e77b1e9be6b0ec4f622032de8a069202b8ffacda7dc2", size = 266369, upload-time = "2026-05-26T06:49:20.174Z" }, + { url = "https://files.pythonhosted.org/packages/49/4a/3ebe6a4c122b2d5340db45cbe7e490663d3228b172710ec71060cd5d541e/zope_interface-8.5-cp311-cp311-win_amd64.whl", hash = "sha256:17a3114bbdddb5e75e5784cdf318944636190cbbc72d357ef9fb1a8b0351f955", size = 215161, upload-time = "2026-05-26T06:49:21.799Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/056ad97af5b16db1975ee98ec7ab03d2ce3f3355efad904ced1dbce0e39f/zope_interface-8.5-cp311-cp311-win_arm64.whl", hash = "sha256:aab6bb5bee10f38ea688b95ba054396b67f613552d2c8378be7fcb2d2fba7646", size = 213481, upload-time = "2026-05-26T06:49:25.085Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/b84123a948f3162a34623e188922827cd845244fdd043ed20f8d02228caa/zope_interface-8.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8e6ee90c2e6de7c37058d5fa41f123c8b13a312db8d1e0fb5840d7f4bcdff9c9", size = 212165, upload-time = "2026-05-26T06:49:26.566Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/cbceec44f1b27208a76c1a688c131302685852406a23df5aab68324109cc/zope_interface-8.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1adc90d3576b3b4c4de4953e6002c37bef28b78d7fa54c1bbfd0c50f022fe7c", size = 212341, upload-time = "2026-05-26T06:49:28.182Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c3/005032195ff3b210c139b7c560ed5c534e844b0907d8e44d2b3d8919305e/zope_interface-8.5-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e6347b8d8d12c5eca6502450a92be30079b7acfade2c4f693efa0deb8871b06e", size = 265296, upload-time = "2026-05-26T06:49:29.741Z" }, + { url = "https://files.pythonhosted.org/packages/c5/66/1036543d6a66bc04c19df3cf650f3ad938a002ab0a443c24e23e8de5e8b9/zope_interface-8.5-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5e970dabea777a24b0b0bbf9dae3ab75ce8b2d8e948edf4875627034b21f3560", size = 270689, upload-time = "2026-05-26T06:49:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/30/4c/8b56259558cace4414e753ca6740396a1f59d4a95ddb55b4658600408670/zope_interface-8.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0b48ccadaa9839e09ff81e969703cecb3f402c813bfe8b958652e699bea69f5", size = 270280, upload-time = "2026-05-26T06:49:33.489Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ea/649908c83aa8fdb7faf2ddca4d3cf6fb8f2157121267dc56e8f72681e26c/zope_interface-8.5-cp312-cp312-win_amd64.whl", hash = "sha256:e0e311f1277468c08fd59a2b41f71b43d25dff639789d364747acd1705c0df6e", size = 215019, upload-time = "2026-05-26T06:49:35.607Z" }, + { url = "https://files.pythonhosted.org/packages/9f/97/da13037b4c563e4df32eedbc819f8c00b754af494f68211e3dffd48d52da/zope_interface-8.5-cp312-cp312-win_arm64.whl", hash = "sha256:652b73107a04159ec6c020db6c1543d4f1e8f4d069bd2aac88a947820923517b", size = 213569, upload-time = "2026-05-26T06:49:37.317Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8c/4c15755d701f2ec0e80d64a18e1ebaf5be2c584c0ec153fd516f5d13eada/zope_interface-8.5-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:28e80457c134d1fa57a7d758004dece348654e1b1467ac22dcdc20fc1d127c52", size = 212512, upload-time = "2026-05-26T06:49:38.996Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2e/4360c54c465db042cc8fbeeec92abac28b4cedbf6ba63c1f092fd08a190f/zope_interface-8.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:09495ce9d559c06b70f2d4855b3e4f48a822a9ddc8be1d30c5b4e5be14ae1ace", size = 212541, upload-time = "2026-05-26T06:49:41.186Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a5/692a2b8d70f78e848793231d5fae5fecbf8d0cccd73430fdc34802a6d3c1/zope_interface-8.5-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:7849ad8fa90763cc1087f4dda78ca3a233e950b3e08fac7079297c9cafbbd7bb", size = 265191, upload-time = "2026-05-26T06:49:43.449Z" }, + { url = "https://files.pythonhosted.org/packages/70/8d/454a9cfc7a050c394ab4f11b3371f7897828b7415e096afff724637e65e0/zope_interface-8.5-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5578c9421ca409a1f39f153d6f7803e4cde01da592ec75a9ac5e1b777d18d33b", size = 270626, upload-time = "2026-05-26T06:49:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/db8409cfa3575b8e9b4800babd7d49f8228433cd1f0c56814bd0ada49c33/zope_interface-8.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e1bd7d96b4ca5fa311f54c9eac16dce4886b428c1531dbe06067763ccdf123b4", size = 270444, upload-time = "2026-05-26T06:49:47.025Z" }, + { url = "https://files.pythonhosted.org/packages/4a/df/a386940e41469ef615e100a216d8b386521e9e598817147f87932ca203c4/zope_interface-8.5-cp313-cp313-win_amd64.whl", hash = "sha256:0c8123d2a4dfde2a613c7cb772605477724782c20bc2e0ad1d9435376a6a44a3", size = 215021, upload-time = "2026-05-26T06:49:48.478Z" }, + { url = "https://files.pythonhosted.org/packages/89/75/477eb5669b6b2a7a843decd1a075e9b1971a8720017654143a7183abd3d9/zope_interface-8.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d02be14f3173c6c7288bc2fdf530090c01c3cf8764ad46c68024686f364278e", size = 213610, upload-time = "2026-05-26T06:49:50.01Z" }, + { url = "https://files.pythonhosted.org/packages/d4/19/5032e954827fdf02db2d2f49737ac4378bb9cfc2cd95a8f2e2a5ae2ec01a/zope_interface-8.5-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:ffaecf013251a89d0de6feb49a46eba48ad8cbbf8a40aeb6045e459e7bec6784", size = 212597, upload-time = "2026-05-26T06:49:51.63Z" }, + { url = "https://files.pythonhosted.org/packages/f1/53/3ef644012cf8a6a234a2d6134aab5a5c65ac5467c86296865501d4fbc406/zope_interface-8.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:126fa9d1c52295ae076d4cf968634f0a1826afa408a20808b57ff72877b8f69f", size = 212626, upload-time = "2026-05-26T06:49:53.236Z" }, + { url = "https://files.pythonhosted.org/packages/32/67/bc8b4f465d388039255003e230c284a175cedf1203c692f23cb7bff64efe/zope_interface-8.5-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:3090e3a663d20194756a59a272e0c8508b889341e31d5894223331fe6b4f9b21", size = 266827, upload-time = "2026-05-26T06:49:54.873Z" }, + { url = "https://files.pythonhosted.org/packages/a7/eb/37d05b935ede53d79690fecc8d201440084418e590bcfc05f384451c7593/zope_interface-8.5-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9342fb74e2afefdb081bf1df727d209ea56995c6e13f5a0540e6d7aff4beafb8", size = 270139, upload-time = "2026-05-26T06:49:57.116Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/fd0c54579e2ce8dc6cf1a757903f3374bc6fbda929a46af9e0f53cb0e5f0/zope_interface-8.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c54725d818f1b57a7efb8b16528326e1f3c257b602b32393fd255c45af8799d", size = 270338, upload-time = "2026-05-26T06:49:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1d/c420dcd777bb761067ea92879ac766694a5ca78608185f1aecea64cbfc11/zope_interface-8.5-cp314-cp314-win_amd64.whl", hash = "sha256:29d74febbae1afeb6834c4ccbf42e242a673c860060f09e53142825270456140", size = 215789, upload-time = "2026-05-26T06:50:00.405Z" }, + { url = "https://files.pythonhosted.org/packages/62/94/50b5eb8f94e527edceac14f9955e58917424ea79bb572ddc18548561cbc2/zope_interface-8.5-cp314-cp314-win_arm64.whl", hash = "sha256:633c8c49396f38df030340797c533e9fe460d1b5d1e42d88e55e938e525f548c", size = 213757, upload-time = "2026-05-26T06:50:01.973Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/5d5f32c4dfcdb16ce2ec5363da686840f13c13e1a1214cb70b49e1cd6d9f/zope_interface-8.5-cp314-cp314t-macosx_10_9_x86_64.whl", hash = "sha256:133999820fdbae513c36c03d6f29ef87317aaa3edef39112222b155083664714", size = 213591, upload-time = "2026-05-26T06:50:03.529Z" }, + { url = "https://files.pythonhosted.org/packages/f3/55/de0c3459ff717fce3342f9a29464c281fdeb0d36c3171ee88d119d5f0650/zope_interface-8.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8bd75c96966e573232f0599deaff717564828031c7f05563ccc1ac35c5ee0304", size = 213733, upload-time = "2026-05-26T06:50:05.101Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/d97430abd5ae9677e8b9295b58720c0064a5b557dbb6b8bf5928484cf0d8/zope_interface-8.5-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:14b0e9799351d4c34fe99afd67f0cdd76e55ba15c66a98699d5fc22ea8241e08", size = 294905, upload-time = "2026-05-26T06:50:07.384Z" }, + { url = "https://files.pythonhosted.org/packages/41/ec/a0f8f3dad6e74992f4654bdd94802be0929eabca7b871cac3b6fbb5e961b/zope_interface-8.5-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cd6a732ac84b94eb1ef9222a117347a27efd294ee16810ffdf7ecd307677ed5", size = 300885, upload-time = "2026-05-26T06:50:08.997Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/6881b48803a0ee8d23eb5efa30fce3ed218a2bd9de5758ce489d224fee81/zope_interface-8.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:798b7c87d0e59a7d5d086d642208d0d8700ff0d55c4029134b3c479c3bfb110f", size = 304672, upload-time = "2026-05-26T06:50:10.563Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0e/b4c01320859ff1d585438bc231fd60bd258d096359bccf6654fecdf0cffb/zope_interface-8.5-cp314-cp314t-win_amd64.whl", hash = "sha256:0fc3a9d45f114d27eaa1e53beeb144533689edca8a9f66505b1e8e8b3f075e42", size = 217241, upload-time = "2026-05-26T06:50:12.171Z" }, +] From d9080517215fcd760050163ad7fccf40ebff5170 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:06:32 +0000 Subject: [PATCH 02/30] test: run the suite without a redis server via fakeredis - fall back to in-process fakeredis under pytest when REDIS_URL is unset - REDIS_URL now required only outside pytest; CI still exercises real redis --- pykeg/config.py | 4 +++- pykeg/settings.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pykeg/config.py b/pykeg/config.py index 2063c2af..fa98fabd 100644 --- a/pykeg/config.py +++ b/pykeg/config.py @@ -123,7 +123,9 @@ def validate(exit_on_error=True): "REDIS_URL", default=os.getenv("REDIS_URL", "redis://localhost:6379/0"), typefn=uristr, - required=True, + # Optional under pytest so the suite can fall back to an in-process + # fakeredis (see settings.py) with no redis server; required otherwise. + required=not IS_RUNNING_PYTEST, ) define_setting("KEGBOT_SECRET_KEY", default=None, required=True) diff --git a/pykeg/settings.py b/pykeg/settings.py index c92b95e2..f8dea5ca 100644 --- a/pykeg/settings.py +++ b/pykeg/settings.py @@ -163,6 +163,19 @@ }, } +# When running the test suite without an explicit REDIS_URL, back every redis +# cache with an in-process fakeredis so the suite needs no redis server. CI sets +# REDIS_URL and continues to exercise a real redis. +if config.IS_RUNNING_PYTEST and "REDIS_URL" not in os.environ: + import fakeredis + + for _cache in CACHES.values(): + if _cache.get("BACKEND") == "django_redis.cache.RedisCache": + _cache["LOCATION"] = "redis://localhost:6379/0" + _cache["OPTIONS"]["CONNECTION_POOL_KWARGS"] = { + "connection_class": fakeredis.FakeConnection + } + INTERNAL_IPS = ("127.0.0.1",) # Set to true if the database admin module should be enabled. From 6de0cf1d0fdab3e57f6bf7ce6aac36b0c927cfab Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:15:06 +0000 Subject: [PATCH 03/30] fix: repair broken datetime conversion in protoutil - DictToProtoMessage referenced an undefined `util.datetime_to_iso8601str` (a NameError if ever hit); use datetime.isoformat() instead - latent since it only triggers on datetime proto fields, which the Stats message (its sole caller) does not have --- pykeg/proto/protoutil.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pykeg/proto/protoutil.py b/pykeg/proto/protoutil.py index 0df9b962..f2268dd5 100644 --- a/pykeg/proto/protoutil.py +++ b/pykeg/proto/protoutil.py @@ -2,8 +2,6 @@ import datetime -TIME_ZONE = "UTC" - def ProtoMessageToDict(message): ret = {} @@ -43,10 +41,10 @@ def DictToProtoMessage(values, out_message): out = getattr(out_message, name) for v in value: if isinstance(v, datetime.datetime): - v = util.datetime_to_iso8601str(v, TIME_ZONE) + v = v.isoformat() out.append(v) else: if isinstance(value, datetime.datetime): - value = util.datetime_to_iso8601str(value, TIME_ZONE) + value = value.isoformat() setattr(out_message, name, value) return out_message From 149c53382f0a86eda52602737859519247113274 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:15:15 +0000 Subject: [PATCH 04/30] lint: adopt ruff for linting and formatting - replace black + flake8 + isort with ruff (check + format), mirroring the taplistio config: line-length 100, select E/F/I, ignore E501, preserve quotes - move dev tooling to the uv dev group; drop flake8/isort config from setup.cfg - reformat the tree and clear unused imports and other lint nits - keep `signal_handlers` import in core apps ready() (registers receivers) and the late template-dirs import behind `# noqa`; exclude generated *_pb2.py - re-export proto exceptions from kbapi via explicit imports + __all__ --- pykeg/api/auth.py | 3 - pykeg/api/models.py | 2 - pykeg/config.py | 2 +- pykeg/core/apps.py | 2 +- pykeg/core/backend_test.py | 1 - .../management/commands/generate_fixtures.py | 4 +- pykeg/core/models.py | 11 +- pykeg/core/stats_test.py | 1 - pykeg/notification/backends/base.py | 1 - pykeg/notification/backends/email_test.py | 12 +- pykeg/notification/notification_test.py | 1 - pykeg/proto/exceptions.py | 70 ++++--- pykeg/proto/kbapi.py | 26 ++- pykeg/proto/util.py | 38 ++-- pykeg/settings.py | 3 +- pykeg/test/plugin.py | 1 - pykeg/util/kbjson.py | 3 +- pykeg/util/units.py | 1 - pykeg/web/account/views.py | 1 - pykeg/web/api/util.py | 1 - pykeg/web/api/validate_jsonp.py | 1 - pykeg/web/auth/__init__.py | 1 - pykeg/web/kegadmin/views.py | 1 - pykeg/web/middleware.py | 1 - pykeg/web/setup_wizard/views.py | 2 +- pyproject.toml | 32 ++- setup.cfg | 13 -- uv.lock | 193 +++--------------- 28 files changed, 149 insertions(+), 279 deletions(-) diff --git a/pykeg/api/auth.py b/pykeg/api/auth.py index f197a955..aff8e4ec 100644 --- a/pykeg/api/auth.py +++ b/pykeg/api/auth.py @@ -1,6 +1,3 @@ -import logging -import re - from rest_framework import authentication from rest_framework.exceptions import AuthenticationFailed diff --git a/pykeg/api/models.py b/pykeg/api/models.py index 71a83623..6b202199 100644 --- a/pykeg/api/models.py +++ b/pykeg/api/models.py @@ -1,3 +1 @@ -from django.db import models - # Create your models here. diff --git a/pykeg/config.py b/pykeg/config.py index fa98fabd..7c721962 100644 --- a/pykeg/config.py +++ b/pykeg/config.py @@ -34,7 +34,7 @@ def boolstr(val): def uristr(val): parsed = urllib.parse.urlparse(val) if not parsed.scheme: - raise ValueError(f"not a URI") + raise ValueError("not a URI") return val diff --git a/pykeg/core/apps.py b/pykeg/core/apps.py index 2783637d..1ae5046c 100644 --- a/pykeg/core/apps.py +++ b/pykeg/core/apps.py @@ -6,4 +6,4 @@ class CoreApp(AppConfig): def ready(self): # Implicitly connect signal handlers decorated with @receiver. - from . import signal_handlers + from . import signal_handlers # noqa: F401 (import registers signal receivers) diff --git a/pykeg/core/backend_test.py b/pykeg/core/backend_test.py index 4cfe412f..809cc49a 100644 --- a/pykeg/core/backend_test.py +++ b/pykeg/core/backend_test.py @@ -3,7 +3,6 @@ from builtins import range from django.test import TransactionTestCase -from django.test.utils import override_settings from pykeg.core import defaults, models from pykeg.web.util import get_base_url diff --git a/pykeg/core/management/commands/generate_fixtures.py b/pykeg/core/management/commands/generate_fixtures.py index b9df3599..6cde8d9e 100644 --- a/pykeg/core/management/commands/generate_fixtures.py +++ b/pykeg/core/management/commands/generate_fixtures.py @@ -54,8 +54,8 @@ def create_objects(self): beverage1 = models.Beverage.objects.create(name="Test Beverage 1", producer=producer1) beverage2 = models.Beverage.objects.create(name="Test Beverage 2", producer=producer2) - keg1 = models.Keg.start_keg("kegboard.flow0", beverage1) - keg2 = models.Keg.start_keg("kegboard.flow1", beverage2) + models.Keg.start_keg("kegboard.flow0", beverage1) + models.Keg.start_keg("kegboard.flow1", beverage2) users = {"alice": None, "bob": None, "carol": None, "mallory": None} for username in users.keys(): diff --git a/pykeg/core/models.py b/pykeg/core/models.py index 064142f8..b0918fa8 100644 --- a/pykeg/core/models.py +++ b/pykeg/core/models.py @@ -73,7 +73,7 @@ class User(AbstractBaseUser): _("superuser status"), default=False, help_text=_( - "Designates that this user has all permissions without " "explicitly assigning them." + "Designates that this user has all permissions without explicitly assigning them." ), ) @@ -81,9 +81,7 @@ class User(AbstractBaseUser): _("username"), max_length=30, unique=True, - help_text=_( - "Required. 30 characters or fewer. Letters, numbers and " "@/./+/-/_ characters" - ), + help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"), validators=[ validators.RegexValidator( re.compile(kb_common.USERNAME_REGEX), _("Enter a valid username."), "invalid" @@ -100,7 +98,7 @@ class User(AbstractBaseUser): is_staff = models.BooleanField( _("staff status"), default=False, - help_text=_("Designates whether the user can log into this admin " "site."), + help_text=_("Designates whether the user can log into this admin site."), ) is_active = models.BooleanField( _("active"), @@ -237,7 +235,6 @@ def send(self): class KegbotSite(models.Model): - VOLUME_DISPLAY_UNITS_CHOICES = ( ("metric", "Metric (mL, L)"), ("imperial", "Imperial (oz, pint)"), @@ -301,7 +298,7 @@ class KegbotSite(models.Model): blank=True, null=True, max_length=64, - help_text="Set to your Google Analytics ID to enable tracking. " "Example: UA-XXXX-y", + help_text="Set to your Google Analytics ID to enable tracking. Example: UA-XXXX-y", ) session_timeout_minutes = models.PositiveIntegerField( default=kb_common.DRINK_SESSION_TIME_MINUTES, diff --git a/pykeg/core/stats_test.py b/pykeg/core/stats_test.py index 6b1ebc07..9e74964e 100644 --- a/pykeg/core/stats_test.py +++ b/pykeg/core/stats_test.py @@ -2,7 +2,6 @@ from addict import Dict from django.test import TransactionTestCase -from django.test.utils import override_settings from . import models from .testutils import make_datetime diff --git a/pykeg/notification/backends/base.py b/pykeg/notification/backends/base.py index 104360da..4b5c1919 100644 --- a/pykeg/notification/backends/base.py +++ b/pykeg/notification/backends/base.py @@ -1,6 +1,5 @@ """Base notification module.""" - from builtins import object diff --git a/pykeg/notification/backends/email_test.py b/pykeg/notification/backends/email_test.py index 0835ba43..027f887f 100644 --- a/pykeg/notification/backends/email_test.py +++ b/pykeg/notification/backends/email_test.py @@ -50,9 +50,7 @@ def test_keg_tapped(self): Track it here: http://test.example.com/kegs/%s/ You are receiving this e-mail because you have notifications enabled -on My Kegbot. To change your settings, visit http://test.example.com/account.""" % ( - keg.id, - ) +on My Kegbot. To change your settings, visit http://test.example.com/account.""" % (keg.id,) self.assertMultiLineEqual(expected_body_plain, msg.body) @@ -69,9 +67,7 @@ def test_keg_tapped(self): You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://test.example.com/account. -

""" % ( - keg.id, - ) +

""" % (keg.id,) self.assertEqual(1, len(msg.alternatives)) self.assertMultiLineEqual(expected_body_html, msg.alternatives[0][0]) @@ -122,9 +118,7 @@ def test_session_started(self): You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://test.example.com/account. -

""" % ( - drink.session.id, - ) +

""" % (drink.session.id,) self.assertEqual(1, len(msg.alternatives)) self.assertMultiLineEqual(expected_body_html, msg.alternatives[0][0]) diff --git a/pykeg/notification/notification_test.py b/pykeg/notification/notification_test.py index 6241d24d..74a86d66 100644 --- a/pykeg/notification/notification_test.py +++ b/pykeg/notification/notification_test.py @@ -1,6 +1,5 @@ """Unittests for notification module.""" -from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from django.test.utils import override_settings diff --git a/pykeg/proto/exceptions.py b/pykeg/proto/exceptions.py index 614fad52..7227d16c 100644 --- a/pykeg/proto/exceptions.py +++ b/pykeg/proto/exceptions.py @@ -1,46 +1,62 @@ class Error(Exception): - """An error occurred.""" - HTTP_CODE = 400 - def __init__(self, message=None): - self.message = message - - def Message(self): - if self.message: - return self.message - m = self.__class__.__doc__ - m = m.split('\n', 1)[0] - return m + """An error occurred.""" + + HTTP_CODE = 400 + + def __init__(self, message=None): + self.message = message + + def Message(self): + if self.message: + return self.message + m = self.__class__.__doc__ + m = m.split('\n', 1)[0] + return m + class NotFoundError(Error): - """The requested object could not be found.""" - HTTP_CODE = 404 + """The requested object could not be found.""" + + HTTP_CODE = 404 + class RequestError(Error): - """There was an error fufilling the request.""" + """There was an error fufilling the request.""" + class ServerError(Error): - """The server had a problem fulfilling your request.""" - HTTP_CODE = 500 + """The server had a problem fulfilling your request.""" + + HTTP_CODE = 500 + class BadRequestError(Error): - """The request was incomplete or malformed.""" - HTTP_CODE = 400 + """The request was incomplete or malformed.""" + + HTTP_CODE = 400 + class NoAuthTokenError(Error): - """An api_key is required.""" - HTTP_CODE = 401 + """An api_key is required.""" + + HTTP_CODE = 401 + class BadApiKeyError(Error): - """The api_key given is invalid.""" - HTTP_CODE = 401 + """The api_key given is invalid.""" + + HTTP_CODE = 401 + class PermissionDeniedError(Error): - """The api_key given does not have permission for this resource.""" - HTTP_CODE = 401 + """The api_key given does not have permission for this resource.""" + + HTTP_CODE = 401 + MAP_NAME_TO_EXCEPTION = dict((c.__name__, c) for c in Error.__subclasses__()) -def ErrorCodeToException(code, message=ServerError): - cls = MAP_NAME_TO_EXCEPTION.get(code, Error) - return cls(message) +def ErrorCodeToException(code, message=ServerError): + cls = MAP_NAME_TO_EXCEPTION.get(code, Error) + return cls(message) diff --git a/pykeg/proto/kbapi.py b/pykeg/proto/kbapi.py index 51d31c9d..4183667a 100644 --- a/pykeg/proto/kbapi.py +++ b/pykeg/proto/kbapi.py @@ -6,9 +6,33 @@ import gflags import requests -from .exceptions import * +from .exceptions import ( + BadApiKeyError, + BadRequestError, + Error, + ErrorCodeToException, + NoAuthTokenError, + NotFoundError, + PermissionDeniedError, + RequestError, + ServerError, +) from .util import AttrDict +# Exceptions are re-exported here so callers can `from pykeg.proto import kbapi` +# and reference e.g. `kbapi.NoAuthTokenError`. +__all__ = [ + "BadApiKeyError", + "BadRequestError", + "Error", + "ErrorCodeToException", + "NoAuthTokenError", + "NotFoundError", + "PermissionDeniedError", + "RequestError", + "ServerError", +] + gflags.DEFINE_float( "api_timeout", 10.0, diff --git a/pykeg/proto/util.py b/pykeg/proto/util.py index 005f51e8..5bc4be3a 100644 --- a/pykeg/proto/util.py +++ b/pykeg/proto/util.py @@ -1,25 +1,25 @@ class AttrDict(dict): - """Dict that exposes items as attributes. + """Dict that exposes items as attributes. - Source: https://stackoverflow.com/a/48806603 - """ + Source: https://stackoverflow.com/a/48806603 + """ - def __init__(self, mapping=None): - super(AttrDict, self).__init__() - if mapping is not None: - for key, value in mapping.items(): - self.__setitem__(key, value) + def __init__(self, mapping=None): + super(AttrDict, self).__init__() + if mapping is not None: + for key, value in mapping.items(): + self.__setitem__(key, value) - def __setitem__(self, key, value): - if isinstance(value, dict): - value = AttrDict(value) - super(AttrDict, self).__setitem__(key, value) - self.__dict__[key] = value # for code completion in editors + def __setitem__(self, key, value): + if isinstance(value, dict): + value = AttrDict(value) + super(AttrDict, self).__setitem__(key, value) + self.__dict__[key] = value # for code completion in editors - def __getattr__(self, item): - try: - return self.__getitem__(item) - except KeyError: - raise AttributeError(item) + def __getattr__(self, item): + try: + return self.__getitem__(item) + except KeyError: + raise AttributeError(item) - __setattr__ = __setitem__ + __setattr__ = __setitem__ diff --git a/pykeg/settings.py b/pykeg/settings.py index f8dea5ca..b386cb85 100644 --- a/pykeg/settings.py +++ b/pykeg/settings.py @@ -9,7 +9,6 @@ import os import dj_database_url -import dj_email_url from pykeg import config from pykeg.config import ENV_PRODUCTION, ENV_TEST @@ -283,7 +282,7 @@ # Storage DEFAULT_FILE_STORAGE = "pykeg.web.kegweb.kbstorage.KegbotFileSystemStorage" -from pykeg.core.util import get_plugin_template_dirs +from pykeg.core.util import get_plugin_template_dirs # noqa: E402 (needs KEGBOT_PLUGINS above) TEMPLATES = [ { diff --git a/pykeg/test/plugin.py b/pykeg/test/plugin.py index 84c9ca2e..83e066ad 100644 --- a/pykeg/test/plugin.py +++ b/pykeg/test/plugin.py @@ -4,7 +4,6 @@ import tempfile import pytest -from django.conf import settings logger = logging.getLogger("pykeg.test.plugin") TEMP_DATA_DIR = None diff --git a/pykeg/util/kbjson.py b/pykeg/util/kbjson.py index 7cace1dc..6dd32ea5 100644 --- a/pykeg/util/kbjson.py +++ b/pykeg/util/kbjson.py @@ -6,7 +6,6 @@ import datetime import json -import types import isodate from addict import Dict @@ -32,7 +31,7 @@ def _ToAttrDict(obj): format used in JSONEncoder. If it does not parse, the value will be left as a string. """ - if type(obj) == dict: + if type(obj) is dict: # Try to convert any "time" or "date" fields into datetime objects. If the # format doesn't match, just leave it alone. for k, v in list(obj.items()): diff --git a/pykeg/util/units.py b/pykeg/util/units.py index 25ba104e..3cab0c74 100644 --- a/pykeg/util/units.py +++ b/pykeg/util/units.py @@ -1,4 +1,3 @@ -import types from builtins import object from enum import Enum diff --git a/pykeg/web/account/views.py b/pykeg/web/account/views.py index 19b3a4f1..c460e49f 100644 --- a/pykeg/web/account/views.py +++ b/pykeg/web/account/views.py @@ -8,7 +8,6 @@ from django.contrib.auth.decorators import login_required from django.http import Http404 from django.shortcuts import redirect, render -from django.urls import reverse from django.views.decorators.cache import never_cache from django.views.decorators.http import require_POST diff --git a/pykeg/web/api/util.py b/pykeg/web/api/util.py index b77a8073..8bd6b9af 100644 --- a/pykeg/web/api/util.py +++ b/pykeg/web/api/util.py @@ -3,7 +3,6 @@ import logging import sys import traceback -import types from addict import Dict from django.conf import settings diff --git a/pykeg/web/api/validate_jsonp.py b/pykeg/web/api/validate_jsonp.py index bf79e6a5..79798f84 100644 --- a/pykeg/web/api/validate_jsonp.py +++ b/pykeg/web/api/validate_jsonp.py @@ -116,7 +116,6 @@ def is_valid_javascript_identifier(identifier, escape=r"\u", ucd_cat=category): return False if escape in identifier: - new = [] add_char = new.append split_id = identifier.split(escape) diff --git a/pykeg/web/auth/__init__.py b/pykeg/web/auth/__init__.py index dc7b5198..2a200fb7 100644 --- a/pykeg/web/auth/__init__.py +++ b/pykeg/web/auth/__init__.py @@ -23,7 +23,6 @@ class UserExistsException(AuthException): class AuthBackend(object): - # Django methods def authenticate(self, **credentials): diff --git a/pykeg/web/kegadmin/views.py b/pykeg/web/kegadmin/views.py index 0d23a73b..9d04f409 100644 --- a/pykeg/web/kegadmin/views.py +++ b/pykeg/web/kegadmin/views.py @@ -21,7 +21,6 @@ from pykeg.backup import backup from pykeg.core import models, tasks -from pykeg.core.util import get_runtime_version_info from pykeg.logging.handlers import RedisListHandler from pykeg.util import kbjson from pykeg.util.email import build_message diff --git a/pykeg/web/middleware.py b/pykeg/web/middleware.py index ed4ea554..d188f296 100644 --- a/pykeg/web/middleware.py +++ b/pykeg/web/middleware.py @@ -5,7 +5,6 @@ from django.shortcuts import render from django.utils import timezone -from pykeg import config from pykeg.core import models from pykeg.core.util import get_version_object, must_upgrade, set_current_request from pykeg.plugin import util as plugin_util diff --git a/pykeg/web/setup_wizard/views.py b/pykeg/web/setup_wizard/views.py index e8312969..64861449 100644 --- a/pykeg/web/setup_wizard/views.py +++ b/pykeg/web/setup_wizard/views.py @@ -168,7 +168,7 @@ def admin(request): form = AdminUserForm(request.POST) if form.is_valid(): form.save() - user = authenticate( + authenticate( username=form.cleaned_data.get("username"), password=form.cleaned_data.get("password"), ) diff --git a/pyproject.toml b/pyproject.toml index d8de99c3..c71e9ecf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,12 +47,10 @@ repository = "https://github.com/Kegbot/kegbot-server" [dependency-groups] dev = [ - "black", - "flake8", - "isort", "pytest", "pytest-django", "requests-mock", + "ruff", "vcrpy", ] @@ -63,15 +61,27 @@ package = true # lock for CI/Docker but skip locally where those libs are absent, e.g.: # uv sync --no-install-package mysqlclient --no-install-package psycopg2 -[tool.black] +[tool.ruff] line-length = 100 -target_version = ['py310'] -include = 'pykeg/.*\.pyi?$' -exclude = ''' -( - .*/migrations/.* -) -''' +target-version = "py310" +exclude = [ + ".git", + ".venv", + "build", + "dist", + "migrations", + # Generated protobuf code (regenerated from kegbot-api .proto sources). + "pykeg/proto/*_pb2.py", +] + +[tool.ruff.format] +quote-style = "preserve" + +[tool.ruff.lint] +# UP (pyupgrade) is added in the Python 3.14 phase; keep this to format + +# pycodestyle/pyflakes/isort for the tooling swap. +select = ["E", "F", "I"] +ignore = ["E501"] [build-system] requires = ["hatchling"] diff --git a/setup.cfg b/setup.cfg index 8146c245..6bbf076e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,19 +1,6 @@ -[flake8] -exclude=build,.git,migrations,settings.py -ignore=E128,E265,E266,E501,W601 -max-line-length=100 - [tool:pytest] addopts = -p pykeg.test.plugin -ra --reuse-db --lfnf=all testpaths = pykeg/ python_files = tests.py test_*.py *_tests.py *_test.py DJANGO_SETTINGS_MODULE = pykeg.settings REUSE_DB = 1 - -[tool:isort] -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 -skip_glob=*migrations* diff --git a/uv.lock b/uv.lock index e4d78554..84b45b23 100644 --- a/uv.lock +++ b/uv.lock @@ -32,50 +32,6 @@ 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 = "black" -version = "26.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pytokens" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/84/b3f55026206a9e8820a91503308075ca48eadc515e436731ca01dbe043b3/black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", size = 1987719, upload-time = "2026-05-18T17:05:02.757Z" }, - { url = "https://files.pythonhosted.org/packages/c6/34/7db312c5e5783d6e76cffd9d5ac8972a32badae4c6e3288dac0eed8d3bed/black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", size = 1810083, upload-time = "2026-05-18T17:05:04.302Z" }, - { url = "https://files.pythonhosted.org/packages/33/e2/e0101e73c2c8727634e2efcb35e2b34bd23ad70dfa673789f5773a591b21/black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", size = 1860633, upload-time = "2026-05-18T17:05:06.391Z" }, - { url = "https://files.pythonhosted.org/packages/b0/4c/e15c0c5b23cf3651035fe5addcce90e283af3548a3f91bb03d81b83106ab/black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", size = 1477886, upload-time = "2026-05-18T17:05:07.96Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3f/59d43ade98d2ce5c8dc34a4e46cbecd177e6d55d7d4092969c6003ccc655/black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", size = 1277111, upload-time = "2026-05-18T17:05:09.473Z" }, - { url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" }, - { url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" }, - { url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" }, - { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, - { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, - { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, - { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, - { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, - { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, - { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, - { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, - { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, - { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, - { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, - { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, -] - [[package]] name = "certifi" version = "2026.7.22" @@ -423,20 +379,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/e2/964e6ef372770dd7c32f9738b50ff4924f1d3cccd665b568680e4bcb0167/fakeredis-2.37.0-py3-none-any.whl", hash = "sha256:657a2a695a1123be0c13f98db409371497bd94c29d260dd76a9fc7ce1a633745", size = 151526, upload-time = "2026-07-22T20:04:51.979Z" }, ] -[[package]] -name = "flake8" -version = "7.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, -] - [[package]] name = "freezegun" version = "1.5.5" @@ -663,15 +605,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] -[[package]] -name = "isort" -version = "8.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, -] - [[package]] name = "kegbot" version = "1.3.0" @@ -715,12 +648,10 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "black" }, - { name = "flake8" }, - { name = "isort" }, { name = "pytest" }, { name = "pytest-django" }, { name = "requests-mock" }, + { name = "ruff" }, { name = "vcrpy" }, ] @@ -764,33 +695,13 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "black" }, - { name = "flake8" }, - { name = "isort" }, { name = "pytest" }, { name = "pytest-django" }, { name = "requests-mock" }, + { name = "ruff" }, { name = "vcrpy" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "mysqlclient" version = "2.2.8" @@ -823,15 +734,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] -[[package]] -name = "pathspec" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, -] - [[package]] name = "pilkit" version = "3.0" @@ -938,15 +840,6 @@ wheels = [ { 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]] -name = "platformdirs" -version = "4.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -983,15 +876,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/4a/a3566f77501c21a6c2a1fc234dbe5dff74a86e3f150ef070e4ffb835e7f9/psycopg2-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:a73d5513bfe929c56555006c7a9cc7ae6e4276aa99dd2b1e2544eb8bb54f8b23", size = 2848588, upload-time = "2026-04-20T23:33:25.983Z" }, ] -[[package]] -name = "pycodestyle" -version = "2.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, -] - [[package]] name = "pycparser" version = "3.0" @@ -1001,15 +885,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] -[[package]] -name = "pyflakes" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -1085,45 +960,6 @@ version = "3.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/df/ec/e31302d355bcb9d207d9b858adc1ecc4a6d8c855730c8ba4ddbdd3f8eb8d/python-gflags-3.1.2.tar.gz", hash = "sha256:40ae131e899ef68e9e14aa53ca063839c34f6a168afe622217b5b875492a1ee2", size = 52093, upload-time = "2017-10-27T13:04:08.546Z" } -[[package]] -name = "pytokens" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, - { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, - { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, - { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, - { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, - { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, - { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, - { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, - { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, - { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, - { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, - { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, - { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, - { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, - { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, - { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, -] - [[package]] name = "pytz" version = "2026.3.post1" @@ -1262,6 +1098,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/35/db396caf7cfe71a9e661cea520b276f1472df25015e7cb9143c65a1dca6d/rq-1.16.2-py3-none-any.whl", hash = "sha256:52e619f6cb469b00e04da74305045d244b75fecb2ecaa4f26422add57d3c5f09", size = 90911, upload-time = "2024-05-01T07:13:11.158Z" }, ] +[[package]] +name = "ruff" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, + { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, + { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, + { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, + { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, + { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, + { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, + { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, + { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, +] + [[package]] name = "six" version = "1.17.0" From 0193d10c59fd7dcf82f53a89baf49f9e9931bf67 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:17:55 +0000 Subject: [PATCH 05/30] chore: modernize pre-commit (sync-with-uv + ruff) - swap sync_with_poetry / isort / black hooks for sync-with-uv + ruff-pre-commit - add pre-commit to the uv dev group; pin the hook to the locked ruff 0.16.1 - drop the py2 `from builtins import *` in bin/kegbot so the hooks pass --- .pre-commit-config.yaml | 28 +++++------ bin/kegbot | 3 +- pyproject.toml | 1 + uv.lock | 100 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 18 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d7caf659..1d45da3b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,22 +1,18 @@ default_language_version: - python: python3.10 + # Bumped to python3.14 in the Python 3.14 phase. + python: python3.11 repos: - - repo: https://github.com/floatingpurr/sync_with_poetry - rev: '0.1.0' # the revision or tag to clone at + - repo: https://github.com/tsvikas/sync-with-uv + rev: v0.4.0 hooks: - - id: sync_with_poetry - args: [] # optional args - additional_dependencies: ['poetry'] + - id: sync-with-uv - - repo: https://github.com/pycqa/isort - rev: 5.10.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.16.1 hooks: - - id: isort - name: isort (python) - - - repo: https://github.com/psf/black - rev: 22.6.0 - hooks: - - id: black - + # Run the linter. + - id: ruff-check + args: [--fix] + # Run the formatter. + - id: ruff-format diff --git a/bin/kegbot b/bin/kegbot index e8aa644e..36b7e4c4 100755 --- a/bin/kegbot +++ b/bin/kegbot @@ -3,7 +3,6 @@ import os import subprocess import sys -from builtins import * import django from django.core import management @@ -13,7 +12,7 @@ from pykeg.util import bugreport if sys.version_info < (2, 7): sys.stderr.write( - "Error: Kegbot needs Python 2.7 or newer.\n\n" "Current version: %s\n" % sys.version + "Error: Kegbot needs Python 2.7 or newer.\n\nCurrent version: %s\n" % sys.version ) sys.exit(1) diff --git a/pyproject.toml b/pyproject.toml index c71e9ecf..784a05a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ repository = "https://github.com/Kegbot/kegbot-server" [dependency-groups] dev = [ + "pre-commit", "pytest", "pytest-django", "requests-mock", diff --git a/uv.lock b/uv.lock index 84b45b23..14a71add 100644 --- a/uv.lock +++ b/uv.lock @@ -81,6 +81,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.9" @@ -210,6 +219,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/6e/c0fcbb7d341a46cf4241a6aa9e6a737734f0657521fc1bcd074953fe4eea/confusable_homoglyphs-3.3.1-py2.py3-none-any.whl", hash = "sha256:84c92cb79dc7f55aa290d0762b2349abd8dee4c16fbe6f99eac978d394e2e6a1", size = 144755, upload-time = "2024-01-30T10:10:24.857Z" }, ] +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + [[package]] name = "dj-database-url" version = "2.2.0" @@ -379,6 +397,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/e2/964e6ef372770dd7c32f9738b50ff4924f1d3cccd665b568680e4bcb0167/fakeredis-2.37.0-py3-none-any.whl", hash = "sha256:657a2a695a1123be0c13f98db409371497bd94c29d260dd76a9fc7ce1a633745", size = 151526, upload-time = "2026-07-22T20:04:51.979Z" }, ] +[[package]] +name = "filelock" +version = "3.32.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/57/3ba6e6cb097f85b855b00163d169f35365f44277df044dcf96d55b8f62a3/filelock-3.32.2.tar.gz", hash = "sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8", size = 217172, upload-time = "2026-07-29T22:46:04.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" }, +] + [[package]] name = "freezegun" version = "1.5.5" @@ -578,6 +605,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, ] +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + [[package]] name = "idna" version = "3.18" @@ -648,6 +684,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-django" }, { name = "requests-mock" }, @@ -695,6 +732,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-django" }, { name = "requests-mock" }, @@ -716,6 +754,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/e5/037d55623be9f681236e04abe12e1290847c06bd48270c3f19ac33493cbf/mysqlclient-2.2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:260cce0e81446c83bf0a389e0fae38d68547d9f8fc0833bc733014e10ce28a99", size = 213067, upload-time = "2026-02-10T10:58:43.389Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "oauthlib" version = "3.3.1" @@ -840,6 +887,15 @@ wheels = [ { 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]] +name = "platformdirs" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -849,6 +905,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, +] + [[package]] name = "protobuf" version = "3.20.0" @@ -954,6 +1026,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/1581a8103855c43567776aa34135e5ec3c597346c23bfd10c7eb5e0b10a4/python_discovery-1.5.1.tar.gz", hash = "sha256:e2ea8b884cd1701f386eda8cf327b87743f1dc21b7f784470799537d95635384", size = 77200, upload-time = "2026-07-31T22:06:02.48Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/07/a89b539750a159d5101c4eb9fc84e2961f65cefbd5e0b7440b284471c0b0/python_discovery-1.5.1-py3-none-any.whl", hash = "sha256:ac07f44cade589d954e9d6a1e1468539fdddd2cf676beb51da73e0f156b7c932", size = 35752, upload-time = "2026-07-31T22:06:01.116Z" }, +] + [[package]] name = "python-gflags" version = "3.1.2" @@ -1235,6 +1319,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/77/cb4219be91508399cbcb6143bad89462cfb16f6c638458f454a5d46ac95a/vcrpy-8.3.0-py3-none-any.whl", hash = "sha256:bd66e6143746778157f00e2a922527a8d96b2fdc350be8988a45a29c843815b9", size = 46530, upload-time = "2026-07-04T14:27:00.546Z" }, ] +[[package]] +name = "virtualenv" +version = "21.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/fa/18004e5cb15541ad2a68ff219c755233b012b12d4ec8663d06a258082bec/virtualenv-21.7.1.tar.gz", hash = "sha256:d0dbfaa5483487baea28d7210ef8d24c9d1bd0f10f449eeb215568825a9b334e", size = 5525237, upload-time = "2026-07-30T15:40:36.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a7/ded126c19495158a05c7202b3389139839d4cf78d622d453867778e0f7a8/virtualenv-21.7.1-py3-none-any.whl", hash = "sha256:6394973f990536e34c05157179146c020284c42fe01da1dfeb0ba16c345280d9", size = 5504576, upload-time = "2026-07-30T15:40:34.512Z" }, +] + [[package]] name = "whitenoise" version = "6.12.0" From 330c11b7640898dbcda40e3a9db1c1d44a23f89a Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:50:04 +0000 Subject: [PATCH 06/30] deps: upgrade to django 5.2 lts - Django 3.2 -> 5.2 LTS, with matching django-cors-headers, django-redis, django-rq/rq, and DRF bumps - django-crispy-forms 1.x -> 2.x plus the crispy-bootstrap4 template pack - drop the unmaintained django-bootstrap-pagination and pytz --- pyproject.toml | 19 ++++---- uv.lock | 123 ++++++++++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 62 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 784a05a5..97d12dc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,15 +10,15 @@ dependencies = [ "coloredlogs", "dj-database-url", "dj-email-url", - "django-bootstrap-pagination", - "django-cors-headers>=3.13.0,<4", - "django-crispy-forms==1.14.0", + "django-cors-headers>=4.9,<5", + "django-crispy-forms>=2.4,<3", + "crispy-bootstrap4>=2024.10", "django-imagekit", - "django-redis", - "django-registration", - "django-rq>=2.5.1,<3", - "djangorestframework>=3.13.1,<4", - "Django==3.2.15", + "django-redis>=5.4", + "django-registration>=5.1", + "django-rq>=3,<5", + "djangorestframework>=3.16,<4", + "Django>=5.2,<5.3", "fakeredis", "freezegun>=1.2.1,<2", "future>=0.18.2,<0.19", @@ -33,12 +33,11 @@ dependencies = [ "protobuf==3.20.0", "psycopg2", "python-gflags", - "pytz", "PyYAML", "redis", "requests", "requests-oauthlib", - "rq>=1.10.1,<2", + "rq>=2,<3", "whitenoise", ] diff --git a/uv.lock b/uv.lock index 14a71add..34c2264b 100644 --- a/uv.lock +++ b/uv.lock @@ -219,6 +219,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/6e/c0fcbb7d341a46cf4241a6aa9e6a737734f0657521fc1bcd074953fe4eea/confusable_homoglyphs-3.3.1-py2.py3-none-any.whl", hash = "sha256:84c92cb79dc7f55aa290d0762b2349abd8dee4c16fbe6f99eac978d394e2e6a1", size = 144755, upload-time = "2024-01-30T10:10:24.857Z" }, ] +[[package]] +name = "crispy-bootstrap4" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "django-crispy-forms" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/cc/638d36595da9fbb2c9d0be98bf6007442a65a521b614cf4645d04311b061/crispy_bootstrap4-2026.2.tar.gz", hash = "sha256:66f8f14bf9c2c16ed94243236ed253a94e5a625afa1ee64022ce29db98c6cd85", size = 34645, upload-time = "2026-02-11T22:45:05.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/6d/b90d601ea2449cc6b35b4b08be90fb1f6ca1baf2be383ed195f7bfa91a32/crispy_bootstrap4-2026.2-py3-none-any.whl", hash = "sha256:4b2b99dfe3e3cacb548702159462110901bd38792b650b770e50c62284ac2227", size = 23178, upload-time = "2026-02-11T22:45:04.108Z" }, +] + +[[package]] +name = "croniter" +version = "6.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/57/2e2a65aee2a70483cb28e2b7e15a072d00a523207593b44400d4717bb100/croniter-6.2.4.tar.gz", hash = "sha256:fc124f751b1b04805c2a04b061898b436b45ab2320b045e1e052ea895de65189", size = 166267, upload-time = "2026-07-10T09:52:59.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/ba/d678e5bd329646ca51d3c92addbc77804e86d21f4b6b6a027218e6abb010/croniter-6.2.4-py3-none-any.whl", hash = "sha256:8ef3d544107a5c05a150a2d78f8bf5a8eb9c5c4d93405a736b824109574e3f4d", size = 46677, upload-time = "2026-07-10T09:52:58.425Z" }, +] + [[package]] name = "distlib" version = "0.4.3" @@ -252,16 +277,16 @@ wheels = [ [[package]] name = "django" -version = "3.2.15" +version = "5.2.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, - { name = "pytz" }, { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/45/371717cbed904aa95864cf2b28beece8e73f6314f1d2b26cba4c2890dbeb/Django-3.2.15.tar.gz", hash = "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b", size = 9833828, upload-time = "2022-08-03T07:38:21.696Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/f9/9ddc8444397ed7e72c52f63b48ecc2849ae1ca4d621776399a81e501ee3c/Django-3.2.15-py3-none-any.whl", hash = "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713", size = 7888258, upload-time = "2022-08-03T07:38:15.296Z" }, + { 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" }, ] [[package]] @@ -276,34 +301,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/e6/4c34d94dfb74bbcbc489606e61f1924933de30d22c593dd1f429f35fbd7f/django_appconf-1.2.0-py3-none-any.whl", hash = "sha256:b81bce5ef0ceb9d84df48dfb623a32235d941c78cc5e45dbb6947f154ea277f4", size = 6500, upload-time = "2025-11-08T15:46:25.957Z" }, ] -[[package]] -name = "django-bootstrap-pagination" -version = "1.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/af/dbf3ad602a9ab111a7709e4a5a916a7ecac92b37ea28b33ca00104770a93/django-bootstrap-pagination-1.7.1.tar.gz", hash = "sha256:69d826d92217325611cb86e49944d8261e3c92eaa4deafea5a605d79fd363883", size = 15843, upload-time = "2019-06-13T21:31:39.323Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/47/eb84f1b4cf6e06a18d3e666a6123301de84dea4af19e2e13edcbeaa83f4a/django_bootstrap_pagination-1.7.1-py3-none-any.whl", hash = "sha256:47e742679cf109e12f10ae09d5263433f94440818cf7b023e90a3f5252849639", size = 15815, upload-time = "2019-06-13T21:31:37.146Z" }, -] - [[package]] name = "django-cors-headers" -version = "3.14.0" +version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "asgiref" }, { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/f4/8b8c3d5e9a0aeea43576fbe623599052a7699abb54378ddb44adb1ef1ed3/django_cors_headers-3.14.0.tar.gz", hash = "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739", size = 24892, upload-time = "2023-02-25T07:20:18.41Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/39/55822b15b7ec87410f34cd16ce04065ff390e50f9e29f31d6d116fc80456/django_cors_headers-4.9.0.tar.gz", hash = "sha256:fe5d7cb59fdc2c8c646ce84b727ac2bca8912a247e6e68e1fb507372178e59e8", size = 21458, upload-time = "2025-09-18T10:40:52.326Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/f1/972b3a183192841b811e4b7496de0a31c09ae1d83a5391743c8ae0cbd86e/django_cors_headers-3.14.0-py3-none-any.whl", hash = "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e", size = 13187, upload-time = "2023-02-25T07:20:16.376Z" }, + { url = "https://files.pythonhosted.org/packages/30/d8/19ed1e47badf477d17fb177c1c19b5a21da0fd2d9f093f23be3fb86c5fab/django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449", size = 12809, upload-time = "2025-09-18T10:40:50.843Z" }, ] [[package]] name = "django-crispy-forms" -version = "1.14.0" +version = "2.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/96/56854b9f0b437454cef5dd5e9977017a81c23e235eceb6f7ea9c7848d06d/django-crispy-forms-1.14.0.tar.gz", hash = "sha256:35887b8851a931374dd697207a8f56c57a9c5cb9dbf0b9fa54314da5666cea5b", size = 71229, upload-time = "2022-01-25T08:08:35.943Z" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/f5/b79e3ed7cae871d5d71bf3448d627e1fabab769358bb90808bec056556fe/django_crispy_forms-2.7.tar.gz", hash = "sha256:4c59bed60417375cba26cebb2c67ab350b655934670270b1c89dbcd7e60f1b4c", size = 1097842, upload-time = "2026-07-29T11:12:12.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/34/f5240bcecb9c0d87fc339edf66f87d2ca5580e995ca966a93fdfb5ba7a66/django_crispy_forms-1.14.0-py3-none-any.whl", hash = "sha256:bc4d2037f6de602d39c0bc452ac3029d1f5d65e88458872cc4dbc01c3a400604", size = 133307, upload-time = "2022-01-25T08:08:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/1f/44/50335d09c4deb70affee0c54feef07ba458df015e32e45490615d9dc5b6b/django_crispy_forms-2.7-py3-none-any.whl", hash = "sha256:42a7ecb05ac3fd050d006dfe7aeceb7f318c30e5b5124ff619e2be252f36f096", size = 31478, upload-time = "2026-07-29T11:12:11.052Z" }, ] [[package]] @@ -334,41 +354,41 @@ wheels = [ [[package]] name = "django-registration" -version = "3.4" +version = "5.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "confusable-homoglyphs" }, { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/00/ee3e5e08cb16475836f9ad117631f598f62a4e32a37c01ab4cb0859454ea/django-registration-3.4.tar.gz", hash = "sha256:1a0ccef7ef71e67a78a551abd8ad378977dc14a036f1fcd8be422a68bd5254a9", size = 89040, upload-time = "2023-07-04T01:25:38.448Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/a0/f6e6d0a59b94eb4ab14983853334cb25401d677c5a3799aaca6819acf2eb/django_registration-5.2.1.tar.gz", hash = "sha256:06864f9da0bc4d7b073fb2da98d95d12428357ab0bc46e33f79c26707ec06bbe", size = 94187, upload-time = "2025-04-07T05:54:48.558Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/3a/455455a208cd38c7fececec136e0de4a848004a7dafe4d62e55566dcbbfe/django_registration-3.4-py3-none-any.whl", hash = "sha256:fa76df481189794f47eb73043ee5cc9bfb0963194b901d7bd8cf914beab1ddd0", size = 96588, upload-time = "2023-07-04T01:25:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/78/5d/aa2f82b3c809db66eb986963db1d4b89de190b4eac2e3021a725112e3b13/django_registration-5.2.1-py3-none-any.whl", hash = "sha256:7079e2364b15fc6bef18b81ae94c5e947b556abdbbfb19c9bcca14feafde6a3d", size = 105277, upload-time = "2025-04-07T05:54:46.981Z" }, ] [[package]] name = "django-rq" -version = "2.10.3" +version = "4.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, { name = "redis" }, { name = "rq" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/65/3727ab9f92c780fb23e8c9939097384fb5ff66e6127a4225b58c57f8051c/django-rq-2.10.3.tar.gz", hash = "sha256:572f2e142a4e4a2f68b3601eef6ddfb041c470dfc7028c975981f7bb942e184c", size = 49043, upload-time = "2024-11-03T09:08:43.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/9d/7989e0e4df4ea4af1c95a1a6b751bdfe68d1ac0b15465fc1a0bcb679d551/django_rq-4.1.1.tar.gz", hash = "sha256:6976b8f50d22d844d350b2f0a26b209889bd60ece257b9888d4e5b7d17f622c3", size = 48776, upload-time = "2026-07-04T15:11:07.63Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/bb/f3822670a8cf7d815aa8b7f61d02190731697cbf1bd4cbe0db5bf5a1f122/django_rq-2.10.3-py2.py3-none-any.whl", hash = "sha256:f73da09708caf4abc4b16800acf762a00e59885eb8f7a94a83bf06ee2a40b26b", size = 62466, upload-time = "2024-11-03T09:08:41.126Z" }, + { url = "https://files.pythonhosted.org/packages/d7/32/512dfa80fce6390deb390da9492bcbabc4824b3f4574890e68daac13b2d4/django_rq-4.1.1-py3-none-any.whl", hash = "sha256:d10a14eaab06aebd223f0a7e5194d129ea126ac9236125150b2ff86e30f27dea", size = 73412, upload-time = "2026-07-04T15:11:06.068Z" }, ] [[package]] name = "djangorestframework" -version = "3.15.1" +version = "3.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/60/cc2dd985400293fe7bf3fa1b9a5d61f5b44200c33f7d31952f2c9fd79e8a/djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1", size = 1066194, upload-time = "2024-03-22T15:50:51.875Z" } +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/c0/7e/8c45ea7f85dd5d52ceddbacc6f56ecaca21ecbfc0e8c34c95618a14d5082/djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6", size = 1067096, upload-time = "2024-03-22T15:50:48.759Z" }, + { 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]] @@ -648,10 +668,10 @@ source = { editable = "." } dependencies = [ { name = "addict" }, { name = "coloredlogs" }, + { name = "crispy-bootstrap4" }, { name = "dj-database-url" }, { name = "dj-email-url" }, { name = "django" }, - { name = "django-bootstrap-pagination" }, { name = "django-cors-headers" }, { name = "django-crispy-forms" }, { name = "django-imagekit" }, @@ -673,7 +693,6 @@ dependencies = [ { name = "protobuf" }, { name = "psycopg2" }, { name = "python-gflags" }, - { name = "pytz" }, { name = "pyyaml" }, { name = "redis" }, { name = "requests" }, @@ -696,17 +715,17 @@ dev = [ requires-dist = [ { name = "addict" }, { name = "coloredlogs" }, + { name = "crispy-bootstrap4", specifier = ">=2024.10" }, { name = "dj-database-url" }, { name = "dj-email-url" }, - { name = "django", specifier = "==3.2.15" }, - { name = "django-bootstrap-pagination" }, - { name = "django-cors-headers", specifier = ">=3.13.0,<4" }, - { name = "django-crispy-forms", specifier = "==1.14.0" }, + { name = "django", specifier = ">=5.2,<5.3" }, + { name = "django-cors-headers", specifier = ">=4.9,<5" }, + { name = "django-crispy-forms", specifier = ">=2.4,<3" }, { name = "django-imagekit" }, - { name = "django-redis" }, - { name = "django-registration" }, - { name = "django-rq", specifier = ">=2.5.1,<3" }, - { name = "djangorestframework", specifier = ">=3.13.1,<4" }, + { name = "django-redis", specifier = ">=5.4" }, + { name = "django-registration", specifier = ">=5.1" }, + { name = "django-rq", specifier = ">=3,<5" }, + { name = "djangorestframework", specifier = ">=3.16,<4" }, { name = "fakeredis" }, { name = "freezegun", specifier = ">=1.2.1,<2" }, { name = "future", specifier = ">=0.18.2,<0.19" }, @@ -721,12 +740,11 @@ requires-dist = [ { name = "protobuf", specifier = "==3.20.0" }, { name = "psycopg2" }, { name = "python-gflags" }, - { name = "pytz" }, { name = "pyyaml" }, { name = "redis" }, { name = "requests" }, { name = "requests-oauthlib" }, - { name = "rq", specifier = ">=1.10.1,<2" }, + { name = "rq", specifier = ">=2,<3" }, { name = "whitenoise" }, ] @@ -1044,15 +1062,6 @@ version = "3.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/df/ec/e31302d355bcb9d207d9b858adc1ecc4a6d8c855730c8ba4ddbdd3f8eb8d/python-gflags-3.1.2.tar.gz", hash = "sha256:40ae131e899ef68e9e14aa53ca063839c34f6a168afe622217b5b875492a1ee2", size = 52093, upload-time = "2017-10-27T13:04:08.546Z" } -[[package]] -name = "pytz" -version = "2026.3.post1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -1171,15 +1180,16 @@ wheels = [ [[package]] name = "rq" -version = "1.16.2" +version = "2.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, + { name = "croniter" }, { name = "redis" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/1d/a020e8f3559293635dd6283d037bfc15f866136498da3db1e5416c65773d/rq-1.16.2.tar.gz", hash = "sha256:5c5b9ad5fbaf792b8fada25cc7627f4d206a9a4455aced371d4f501cc3f13b34", size = 629881, upload-time = "2024-05-01T07:13:14.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/a8/7bf65cda593feb5888214a4d1e64aae6fc5eb0bbbb74df922c916099a3e5/rq-2.10.0.tar.gz", hash = "sha256:2d8c533dd27500fedabec06295f18db595966e4f22744e6988fe31155b8f7a21", size = 754610, upload-time = "2026-06-20T03:11:45.919Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/35/db396caf7cfe71a9e661cea520b276f1472df25015e7cb9143c65a1dca6d/rq-1.16.2-py3-none-any.whl", hash = "sha256:52e619f6cb469b00e04da74305045d244b75fecb2ecaa4f26422add57d3c5f09", size = 90911, upload-time = "2024-05-01T07:13:11.158Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d3/f8c59750a1c98f99d225954eb2c3dd89f2d66b666c448b4b0dff79ab29ed/rq-2.10.0-py3-none-any.whl", hash = "sha256:1f072e0ff79771f3d3a810170df4c4836dca9ce9f31330a9fc25e25277620ec8", size = 124665, upload-time = "2026-06-20T03:11:47.524Z" }, ] [[package]] @@ -1297,6 +1307,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +] + [[package]] name = "urllib3" version = "2.7.0" From 0cd4aa3a59e39da23b36d9eb95ca103f03392ee4 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:50:28 +0000 Subject: [PATCH 07/30] django: adapt to django 5.2 removals and behavior changes - settings: migrate DEFAULT_FILE_STORAGE/STATICFILES_STORAGE to STORAGES, set the crispy bootstrap4 pack, add scheme to CSRF_TRUSTED_ORIGINS - switch SESSION_SERIALIZER to JSONSerializer (pickle serializer removed); existing sessions are invalidated on upgrade - code: url() -> re_path(), get_storage_class() -> default_storage, timezone.utc -> datetime.timezone.utc, pytz -> zoneinfo - guard _keg_pre_save against reverse-relation use on an unsaved keg - django-rq now defers enqueue to on_db_commit; use auto commit mode under pytest so synchronous jobs still run inline within TestCase --- pykeg/core/models.py | 13 +- pykeg/core/stats.py | 4 +- pykeg/core/testutils.py | 3 +- pykeg/core/timezones.py | 449 +++++++++++++++++++++ pykeg/plugin/util.py | 4 +- pykeg/settings.py | 33 +- pykeg/web/kegadmin/views.py | 4 +- pykeg/web/kegweb/templatetags/kegweblib.py | 8 +- 8 files changed, 497 insertions(+), 21 deletions(-) create mode 100644 pykeg/core/timezones.py diff --git a/pykeg/core/models.py b/pykeg/core/models.py index b0918fa8..7c0791c7 100644 --- a/pykeg/core/models.py +++ b/pykeg/core/models.py @@ -7,7 +7,6 @@ from builtins import object, str from uuid import uuid4 -import pytz from addict import Dict from django.conf import settings from django.contrib.auth.models import AbstractBaseUser, UserManager @@ -34,6 +33,7 @@ signals, time_series, ) +from pykeg.core.timezones import COMMON_TIMEZONES from pykeg.core.util import CtoF, get_version from pykeg.util import kbjson, units from pykeg.util.email import build_message @@ -42,7 +42,11 @@ """Django models definition for the kegbot database.""" -TIMEZONE_CHOICES = ((z, z) for z in pytz.common_timezones) +# TODO(temporary): COMMON_TIMEZONES is the historical pytz.common_timezones list, +# vendored so this field's `choices` stay byte-for-byte identical and we avoid a +# data-only migration during the 2.0 upgrade. Replace with a zoneinfo-derived +# list (and ship the accompanying migration) as a follow-up. +TIMEZONE_CHOICES = ((z, z) for z in COMMON_TIMEZONES) logger = logging.getLogger(__name__) @@ -1278,6 +1282,11 @@ def _keg_pre_save(sender, instance, **kwargs): if keg.status == keg.STATUS_ON_TAP: return + # A brand-new (unsaved) keg has no drinks yet; Django 5.x also forbids + # using a reverse relation before the instance has a primary key. + if keg.pk is None: + return + # Determine first drink date & set keg start date to it if earlier. drinks = keg.drinks.all().order_by("time") if drinks: diff --git a/pykeg/core/stats.py b/pykeg/core/stats.py index 56f71b24..08357e16 100644 --- a/pykeg/core/stats.py +++ b/pykeg/core/stats.py @@ -3,9 +3,9 @@ import copy import inspect import logging +import zoneinfo from builtins import object, str -import pytz from addict import Dict from django.utils.timezone import localtime @@ -117,7 +117,7 @@ def greatest_volume_id(self, drink, previous_stats, previous_value=0): def volume_by_day_of_week(self, drink, previous_stats, previous_value={}): ret = copy.copy(previous_value) - tz = pytz.timezone(drink.session.timezone) + tz = zoneinfo.ZoneInfo(drink.session.timezone) local_time = localtime(drink.session.start_time, timezone=tz) drink_weekday = str(local_time.strftime("%w")) ret[drink_weekday] = ret.get(drink_weekday, 0) + drink.volume_ml diff --git a/pykeg/core/testutils.py b/pykeg/core/testutils.py index da4094f3..36ce2d23 100644 --- a/pykeg/core/testutils.py +++ b/pykeg/core/testutils.py @@ -5,7 +5,6 @@ import vcr from django.conf import settings -from django.utils import timezone TESTDATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../testdata/")) CASSETTE_DIR = os.path.join(TESTDATA_DIR, "request_fixtures") @@ -17,7 +16,7 @@ def get_filename(f): def make_datetime(*args): if settings.USE_TZ: - return datetime.datetime(*args, tzinfo=timezone.utc) + return datetime.datetime(*args, tzinfo=datetime.timezone.utc) else: return datetime.datetime(*args) diff --git a/pykeg/core/timezones.py b/pykeg/core/timezones.py new file mode 100644 index 00000000..82f900f1 --- /dev/null +++ b/pykeg/core/timezones.py @@ -0,0 +1,449 @@ +"""Curated list of common time zones for the KegbotSite.timezone field. + +TODO(temporary): this is the historical pytz.common_timezones list, vendored so +the field's choices stay stable (avoiding a data-only migration) after moving +from pytz to zoneinfo. Time-zone math elsewhere uses stdlib zoneinfo. Replace +with a zoneinfo-derived list (plus its migration) as a follow-up. +""" + +COMMON_TIMEZONES = [ + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Fort_Nelson", + "America/Fortaleza", + "America/Glace_Bay", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Nuuk", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Colombo", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kathmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qostanay", + "Asia/Qyzylorda", + "Asia/Riyadh", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ulaanbaatar", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yangon", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faroe", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/Perth", + "Australia/Sydney", + "Canada/Atlantic", + "Canada/Central", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Ulyanovsk", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "GMT", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Kanton", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Wake", + "Pacific/Wallis", + "US/Alaska", + "US/Arizona", + "US/Central", + "US/Eastern", + "US/Hawaii", + "US/Mountain", + "US/Pacific", + "UTC", +] diff --git a/pykeg/plugin/util.py b/pykeg/plugin/util.py index fa8cb79f..3b9fb26d 100644 --- a/pykeg/plugin/util.py +++ b/pykeg/plugin/util.py @@ -3,8 +3,8 @@ from importlib import import_module from django.conf import settings -from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured +from django.urls import re_path from django.utils import timezone from .plugin import Plugin @@ -63,7 +63,7 @@ def _to_urls(urllist, short_name): for regex, fn, viewname in urllist: regex = "plugin/%s/%s" % (short_name, regex) viewname = "plugin-%s-%s" % (short_name, viewname) - urls.append(url(regex, fn, name=viewname)) + urls.append(re_path(regex, fn, name=viewname)) return urls diff --git a/pykeg/settings.py b/pykeg/settings.py index b386cb85..b32f3d85 100644 --- a/pykeg/settings.py +++ b/pykeg/settings.py @@ -47,7 +47,7 @@ "django.contrib.sessions", "django.contrib.staticfiles", "crispy_forms", - "bootstrap_pagination", + "crispy_bootstrap4", "imagekit", "gunicorn", "corsheaders", @@ -72,13 +72,27 @@ # During tests, whitenoise's manifest will not be available and # errors will be thrown while trying to access static files. # Use the default static backend to work around this. - STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" + _STATICFILES_BACKEND = "django.contrib.staticfiles.storage.StaticFilesStorage" else: - STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + _STATICFILES_BACKEND = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +# Storage backends (Django 5.1+ replaced DEFAULT_FILE_STORAGE / STATICFILES_STORAGE). +STORAGES = { + "default": { + "BACKEND": "pykeg.web.kegweb.kbstorage.KegbotFileSystemStorage", + }, + "staticfiles": { + "BACKEND": _STATICFILES_BACKEND, + }, +} + +# crispy-forms 2.x template pack (Bootstrap 4). +CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap4" +CRISPY_TEMPLATE_PACK = "bootstrap4" # Default session serialization. -SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer" +SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer" # Kegweb specific stuff @@ -197,6 +211,11 @@ }, } +# django-rq defers enqueue until the DB transaction commits. Under pytest the +# suite runs inside TestCase (which rolls back and never fires on_commit), so +# enqueue immediately there to keep synchronous (ASYNC=False) jobs running inline. +RQ = {"COMMIT_MODE": "auto" if config.IS_RUNNING_PYTEST else "on_db_commit"} + # logging LOGGING = { @@ -279,8 +298,7 @@ # Imagekit IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND = "imagekit.imagecache.NonValidatingImageCacheBackend" -# Storage -DEFAULT_FILE_STORAGE = "pykeg.web.kegweb.kbstorage.KegbotFileSystemStorage" +# Storage is configured via STORAGES above (Django 5.1+). from pykeg.core.util import get_plugin_template_dirs # noqa: E402 (needs KEGBOT_PLUGINS above) @@ -329,7 +347,8 @@ "http://127.0.0.1:1234", ] CSRF_TRUSTED_ORIGINS = [ - "localhost:1234", + "http://localhost:1234", + "http://127.0.0.1:1234", ] CORS_ALLOW_CREDENTIALS = True SESSION_COOKIE_SAMESITE = None diff --git a/pykeg/web/kegadmin/views.py b/pykeg/web/kegadmin/views.py index 9d04f409..845f0a31 100644 --- a/pykeg/web/kegadmin/views.py +++ b/pykeg/web/kegadmin/views.py @@ -11,7 +11,7 @@ import redis from django.conf import settings from django.contrib import messages -from django.core.files.storage import get_storage_class +from django.core.files.storage import default_storage from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db.models import Q from django.http import Http404, HttpResponse @@ -147,7 +147,7 @@ def email(request): def export(request): context = {} backups = [] - storage = get_storage_class()() + storage = default_storage if request.method == "POST": if "package_backup" in request.POST: diff --git a/pykeg/web/kegweb/templatetags/kegweblib.py b/pykeg/web/kegweb/templatetags/kegweblib.py index bdb4dac7..2f4e0368 100644 --- a/pykeg/web/kegweb/templatetags/kegweblib.py +++ b/pykeg/web/kegweb/templatetags/kegweblib.py @@ -1,6 +1,6 @@ +import zoneinfo from builtins import str -import pytz from django.conf import settings from django.template import ( Library, @@ -141,9 +141,9 @@ def render(self, context): # Try to set time zone information. if settings.TIME_ZONE and not settings.USE_TZ: try: - tz = pytz.timezone(settings.TIME_ZONE) - ts = tz.localize(ts) - except pytz.UnknownTimeZoneError: + tz = zoneinfo.ZoneInfo(settings.TIME_ZONE) + ts = ts.replace(tzinfo=tz) + except zoneinfo.ZoneInfoNotFoundError: pass iso = ts.isoformat() From 481593f002af72dcc9fe6d53dbf5d2a4d3838770 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 06:50:36 +0000 Subject: [PATCH 08/30] web: replace django-bootstrap-pagination with a paginator partial - add a Bootstrap 3 pagination include backed by Django's own Paginator (elided page range + querystring-preserving links) - swap the 10 {% bootstrap_paginate %} usages over to the new include --- .../templates/kegadmin/beer_type_list.html | 3 +- .../templates/kegadmin/brewer_list.html | 3 +- .../templates/kegadmin/drink_list.html | 3 +- .../kegadmin/templates/kegadmin/keg_list.html | 3 +- .../templates/kegadmin/token_list.html | 3 +- .../templates/kegadmin/user_list.html | 3 +- .../kegweb/templates/kegweb/_pagination.html | 33 +++++++++++++++++++ .../templates/kegweb/drinker_sessions.html | 3 +- .../kegweb/drinkingsession_archive_base.html | 3 +- .../web/kegweb/templates/kegweb/keg_list.html | 3 +- .../kegweb/templates/kegweb/keg_sessions.html | 3 +- .../web/kegweb/templatetags/paginator_tags.py | 17 ++++++++++ 12 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 pykeg/web/kegweb/templates/kegweb/_pagination.html create mode 100644 pykeg/web/kegweb/templatetags/paginator_tags.py diff --git a/pykeg/web/kegadmin/templates/kegadmin/beer_type_list.html b/pykeg/web/kegadmin/templates/kegadmin/beer_type_list.html index ef340e22..c12b8fab 100644 --- a/pykeg/web/kegadmin/templates/kegadmin/beer_type_list.html +++ b/pykeg/web/kegadmin/templates/kegadmin/beer_type_list.html @@ -1,7 +1,6 @@ {% extends "kegadmin/base.html" %} {% load kegweblib %} {% load crispy_forms_tags %} -{% load bootstrap_pagination %} {% block title %}Kegbot Admin: Beer Types | {{ block.super }}{% endblock %} {% block pagetitle %}Kegbot Admin: Beer Types{% endblock %} @@ -35,6 +34,6 @@ -{% bootstrap_paginate beverages %} +{% include "kegweb/_pagination.html" with page=beverages %} {% endblock %} diff --git a/pykeg/web/kegadmin/templates/kegadmin/brewer_list.html b/pykeg/web/kegadmin/templates/kegadmin/brewer_list.html index 8f957643..ec3d073e 100644 --- a/pykeg/web/kegadmin/templates/kegadmin/brewer_list.html +++ b/pykeg/web/kegadmin/templates/kegadmin/brewer_list.html @@ -1,7 +1,6 @@ {% extends "kegadmin/base.html" %} {% load kegweblib %} {% load crispy_forms_tags %} -{% load bootstrap_pagination %} {% block title %}Kegbot Admin: Brewers | {{ block.super }}{% endblock %} {% block pagetitle %}Kegbot Admin: Brewers{% endblock %} @@ -38,6 +37,6 @@ -{% bootstrap_paginate brewers %} +{% include "kegweb/_pagination.html" with page=brewers %} {% endblock %} diff --git a/pykeg/web/kegadmin/templates/kegadmin/drink_list.html b/pykeg/web/kegadmin/templates/kegadmin/drink_list.html index d8de9b84..801d7acb 100644 --- a/pykeg/web/kegadmin/templates/kegadmin/drink_list.html +++ b/pykeg/web/kegadmin/templates/kegadmin/drink_list.html @@ -1,7 +1,6 @@ {% extends "kegadmin/base.html" %} {% load kegweblib %} {% load crispy_forms_tags %} -{% load bootstrap_pagination %} {% block title %}Kegbot Admin: Drinks | {{ block.super }}{% endblock %} {% block pagetitle %}Kegbot Admin: Drinks{% endblock %} @@ -9,7 +8,7 @@ {% block kegadmin-main %} {% crispy delete_drinks_form %} -{% bootstrap_paginate drinks %} +{% include "kegweb/_pagination.html" with page=drinks %} {% endblock %} diff --git a/pykeg/web/kegadmin/templates/kegadmin/keg_list.html b/pykeg/web/kegadmin/templates/kegadmin/keg_list.html index a4435101..4242d03c 100644 --- a/pykeg/web/kegadmin/templates/kegadmin/keg_list.html +++ b/pykeg/web/kegadmin/templates/kegadmin/keg_list.html @@ -1,7 +1,6 @@ {% extends "kegadmin/base.html" %} {% load kegweblib %} {% load crispy_forms_tags %} -{% load bootstrap_pagination %} {% block title %}Kegbot Admin: Kegs | {{ block.super }}{% endblock %} {% block pagetitle %}Kegbot Admin: Kegs{% endblock %} @@ -36,6 +35,6 @@
{{ keg.type.name }}
{% endfor %} -{% bootstrap_paginate kegs %} +{% include "kegweb/_pagination.html" with page=kegs %} {% endblock %} diff --git a/pykeg/web/kegadmin/templates/kegadmin/token_list.html b/pykeg/web/kegadmin/templates/kegadmin/token_list.html index b520da0c..caea7de9 100644 --- a/pykeg/web/kegadmin/templates/kegadmin/token_list.html +++ b/pykeg/web/kegadmin/templates/kegadmin/token_list.html @@ -1,7 +1,6 @@ {% extends "kegadmin/base.html" %} {% load kegweblib %} {% load crispy_forms_tags %} -{% load bootstrap_pagination %} {% block title %}Kegbot Admin: Users | {{ block.super }}{% endblock %} {% block pagetitle %}Kegbot Admin: Users{% endblock %} @@ -40,6 +39,6 @@ -{% bootstrap_paginate tokens %} +{% include "kegweb/_pagination.html" with page=tokens %} {% endblock %} diff --git a/pykeg/web/kegadmin/templates/kegadmin/user_list.html b/pykeg/web/kegadmin/templates/kegadmin/user_list.html index e0f468b3..b3dd47d6 100644 --- a/pykeg/web/kegadmin/templates/kegadmin/user_list.html +++ b/pykeg/web/kegadmin/templates/kegadmin/user_list.html @@ -1,7 +1,6 @@ {% extends "kegadmin/base.html" %} {% load kegweblib %} {% load crispy_forms_tags %} -{% load bootstrap_pagination %} {% block title %}Kegbot Admin: Users | {{ block.super }}{% endblock %} {% block pagetitle %}Kegbot Admin: Users{% endblock %} @@ -50,6 +49,6 @@ -{% bootstrap_paginate users %} +{% include "kegweb/_pagination.html" with page=users %} {% endblock %} diff --git a/pykeg/web/kegweb/templates/kegweb/_pagination.html b/pykeg/web/kegweb/templates/kegweb/_pagination.html new file mode 100644 index 00000000..66a8abe9 --- /dev/null +++ b/pykeg/web/kegweb/templates/kegweb/_pagination.html @@ -0,0 +1,33 @@ +{% comment %} +Bootstrap 3 pagination for a Django Paginator Page, passed in as `page`. +Usage: {% include "kegweb/_pagination.html" with page=page_obj %} +{% endcomment %} +{% load paginator_tags %} +{% if page.paginator.num_pages > 1 %} +
    + + {% if page.has_previous %} + « + {% else %} + + {% endif %} + + {% elided_page_range page as page_numbers %} + {% for num in page_numbers %} + {% if num == page.paginator.ELLIPSIS %} +
  • {{ num }}
  • + {% elif num == page.number %} +
  • {{ num }}
  • + {% else %} +
  • {{ num }}
  • + {% endif %} + {% endfor %} + + {% if page.has_next %} + » + {% else %} + + {% endif %} + +
+{% endif %} diff --git a/pykeg/web/kegweb/templates/kegweb/drinker_sessions.html b/pykeg/web/kegweb/templates/kegweb/drinker_sessions.html index 732a986e..56b6827b 100644 --- a/pykeg/web/kegweb/templates/kegweb/drinker_sessions.html +++ b/pykeg/web/kegweb/templates/kegweb/drinker_sessions.html @@ -1,6 +1,5 @@ {% extends "base.html" %} {% load kegweblib %} -{% load bootstrap_pagination %} {% block title %}Drinker Sessions: {{ drinker.username }} | {{ block.super }}{% endblock %} {% block pagetitle %}Drinker Sessions: {{ drinker.username }}{% endblock %} @@ -24,7 +23,7 @@ {% include "kegweb/keg-session.html" %} {% endwith %} {% endfor %} - {% bootstrap_paginate chunks %} + {% include "kegweb/_pagination.html" with page=chunks %} {% endif %} diff --git a/pykeg/web/kegweb/templates/kegweb/drinkingsession_archive_base.html b/pykeg/web/kegweb/templates/kegweb/drinkingsession_archive_base.html index 36dd8b1a..f9e65978 100644 --- a/pykeg/web/kegweb/templates/kegweb/drinkingsession_archive_base.html +++ b/pykeg/web/kegweb/templates/kegweb/drinkingsession_archive_base.html @@ -1,6 +1,5 @@ {% extends "base.html" %} {% load kegweblib %} -{% load bootstrap_pagination %} {% block content %}
@@ -24,7 +23,7 @@

All Sessions

-{% bootstrap_paginate page_obj range=10 %} +{% include "kegweb/_pagination.html" with page=page_obj %}
diff --git a/pykeg/web/kegweb/templates/kegweb/keg_list.html b/pykeg/web/kegweb/templates/kegweb/keg_list.html index f381c9a8..4a92bbdc 100644 --- a/pykeg/web/kegweb/templates/kegweb/keg_list.html +++ b/pykeg/web/kegweb/templates/kegweb/keg_list.html @@ -1,6 +1,5 @@ {% extends "base.html" %} {% load kegweblib %} -{% load bootstrap_pagination %} {% block title %}Keg List | {{ block.super }}{% endblock %} {% block pagetitle %}Keg List{% endblock %} @@ -15,6 +14,6 @@
{% endfor %} -{% bootstrap_paginate page_obj range=10 %} +{% include "kegweb/_pagination.html" with page=page_obj %} {% endblock %} diff --git a/pykeg/web/kegweb/templates/kegweb/keg_sessions.html b/pykeg/web/kegweb/templates/kegweb/keg_sessions.html index 31a8d963..8fc7b075 100644 --- a/pykeg/web/kegweb/templates/kegweb/keg_sessions.html +++ b/pykeg/web/kegweb/templates/kegweb/keg_sessions.html @@ -1,6 +1,5 @@ {% extends "base.html" %} {% load kegweblib %} -{% load bootstrap_pagination %} {% block title %}Keg {{ keg.id }}: Sessions | {{ block.super }}{% endblock %} {% block pagetitle %}Keg {{ keg.id }}: Sessions{% endblock %} @@ -12,7 +11,7 @@ {% for session in sessions %} {% include "kegweb/keg-session.html" %} {% endfor %} - {% bootstrap_paginate sessions %} + {% include "kegweb/_pagination.html" with page=sessions %} {% endif %} diff --git a/pykeg/web/kegweb/templatetags/paginator_tags.py b/pykeg/web/kegweb/templatetags/paginator_tags.py new file mode 100644 index 00000000..b7f497f2 --- /dev/null +++ b/pykeg/web/kegweb/templatetags/paginator_tags.py @@ -0,0 +1,17 @@ +"""Template helpers for rendering Django Paginator pages. + +Replaces the unmaintained django-bootstrap-pagination package with Django's +own Paginator (see kegweb/_pagination.html for the Bootstrap 3 markup). +""" + +from django import template + +register = template.Library() + + +@register.simple_tag +def elided_page_range(page, on_each_side=2, on_ends=1): + """Return a windowed page range (with ELLIPSIS markers) for a Page.""" + return page.paginator.get_elided_page_range( + page.number, on_each_side=on_each_side, on_ends=on_ends + ) From e1a1a7709c33fd93bbe4adbc1ce5b88533eb878f Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 07:02:15 +0000 Subject: [PATCH 09/30] fix: log the new admin in at the end of the setup wizard - the admin-user step called authenticate() but discarded the result, so the freshly-created admin was never actually signed in - IsSetupMiddleware stubbed the session for all /setup paths; only stub it until the session table exists (created by the wizard's first migrate step), so login() can establish a real session for the admin step - add a test asserting the new admin ends up authenticated --- pykeg/web/middleware.py | 10 ++++++-- pykeg/web/setup_wizard/setup_wizard_tests.py | 24 +++++++++++++++++++- pykeg/web/setup_wizard/views.py | 9 ++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/pykeg/web/middleware.py b/pykeg/web/middleware.py index d188f296..b0304413 100644 --- a/pykeg/web/middleware.py +++ b/pykeg/web/middleware.py @@ -1,6 +1,7 @@ import logging from django.conf import settings +from django.db import connection from django.http import HttpResponse from django.shortcuts import render from django.utils import timezone @@ -104,8 +105,13 @@ def __call__(self, request): # Skip all checks if we're in the setup wizard. if request.path.startswith("/setup"): - request.session = {} - request.session["_auth_user_backend"] = None + # On a fresh install the session table doesn't exist yet, so a real + # session read/write would crash; stub it out until migrations (run + # in the wizard's first step) create the table. Once it exists, keep + # the real session so the wizard can log the new admin in. + if "django_session" not in connection.introspection.table_names(): + request.session = {} + request.session["_auth_user_backend"] = None return self.get_response(request) # First confirm the database is working. diff --git a/pykeg/web/setup_wizard/setup_wizard_tests.py b/pykeg/web/setup_wizard/setup_wizard_tests.py index a541eaa0..84017e8c 100644 --- a/pykeg/web/setup_wizard/setup_wizard_tests.py +++ b/pykeg/web/setup_wizard/setup_wizard_tests.py @@ -3,8 +3,9 @@ from django.core.cache import cache from django.test import TransactionTestCase from django.test.utils import override_settings +from django.urls import reverse -from pykeg.core import defaults +from pykeg.core import defaults, models class SetupWizardTestCase(TransactionTestCase): @@ -32,6 +33,27 @@ def test_settings_debug_true(self): response = self.client.get("/setup/") self.assertEqual(response.status_code, 200) + @override_settings(DEBUG=True) + def test_admin_step_logs_in_new_admin(self): + """The admin-user step creates the admin and logs them in.""" + defaults.set_defaults() # site exists but is not yet set up + + response = self.client.post( + reverse("setup_admin"), + { + "username": "wizadmin", + "email": "admin@example.com", + "password": "s3cret-pass", + "confirm_password": "s3cret-pass", + }, + ) + self.assertRedirects(response, reverse("setup_finish"), fetch_redirect_response=False) + + admin = models.User.objects.get(username="wizadmin") + self.assertTrue(admin.is_superuser) + # The freshly-created admin should now be authenticated. + self.assertEqual(str(admin.pk), self.client.session.get("_auth_user_id")) + def test_setup_not_shown(self): """Verify wizard is not shown on set-up site.""" site = defaults.set_defaults() diff --git a/pykeg/web/setup_wizard/views.py b/pykeg/web/setup_wizard/views.py index 64861449..b39fdb4b 100644 --- a/pykeg/web/setup_wizard/views.py +++ b/pykeg/web/setup_wizard/views.py @@ -4,7 +4,7 @@ from django.conf import settings from django.contrib import messages -from django.contrib.auth import authenticate +from django.contrib.auth import authenticate, login from django.core import management from django.http import Http404 from django.shortcuts import redirect, render @@ -168,10 +168,15 @@ def admin(request): form = AdminUserForm(request.POST) if form.is_valid(): form.save() - authenticate( + # Log the freshly-created admin in so setup continues as them. By + # this step the session table exists (migrated in the first step), + # so IsSetupMiddleware leaves the real session in place. + user = authenticate( username=form.cleaned_data.get("username"), password=form.cleaned_data.get("password"), ) + if user is not None: + login(request, user) return redirect("setup_finish") context["form"] = form return render(request, "setup_wizard/admin.html", context=context) From fdbb22965bac132086f0aee55f8716f010993367 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 07:05:05 +0000 Subject: [PATCH 10/30] server: switch from gunicorn/gevent to waitress - run_server now launches `waitress-serve` (honors $PORT, --threads tunable) - drop the gunicorn config, the deprecated run_gunicorn shim, and the "gunicorn" INSTALLED_APPS entry - removes the gevent C-extension dependency ahead of the Python 3.14 bump --- bin/kegbot | 9 - pykeg/core/management/commands/run_server.py | 25 +- pykeg/settings.py | 1 - pykeg/web/gunicorn_conf.py | 11 - pyproject.toml | 3 +- uv.lock | 278 +------------------ 6 files changed, 25 insertions(+), 302 deletions(-) delete mode 100644 pykeg/web/gunicorn_conf.py diff --git a/bin/kegbot b/bin/kegbot index 36b7e4c4..d8f036b2 100755 --- a/bin/kegbot +++ b/bin/kegbot @@ -1,7 +1,6 @@ #!/usr/bin/env python import os -import subprocess import sys import django @@ -26,14 +25,6 @@ if __name__ == "__main__": # Hack: Django's `version` command cannot be overridden the usual way. print("kegbot-server {}".format(get_version())) sys.exit(0) - elif cmd == "run_gunicorn": - # run_gunicorn was deprecated upstream. - cmd = "gunicorn pykeg.web.wsgi:application {}".format(" ".join(sys.argv[2:])) - sys.stderr.write( - "Warning: run_gunicorn is deprecated, call `{}` instead.\n".format(cmd) - ) - ret = subprocess.call(cmd, shell=True) - sys.exit(ret) elif cmd == "bugreport": ret = bugreport.take_bugreport() sys.exit(ret) diff --git a/pykeg/core/management/commands/run_server.py b/pykeg/core/management/commands/run_server.py index f33d8f11..9f258335 100644 --- a/pykeg/core/management/commands/run_server.py +++ b/pykeg/core/management/commands/run_server.py @@ -1,3 +1,5 @@ +import os + from pykeg.core.management.commands.common import RunnerCommand @@ -6,22 +8,21 @@ class Command(RunnerCommand): pidfile_name = "kegbot_run_server.pid" def add_arguments(self, parser): - super(Command, self).add_arguments(parser) + super().add_arguments(parser) parser.add_argument( - "--gunicorn_options", + "--waitress_options", action="store", - dest="gunicorn_options", - default="-w 3", - help="Specifies extra options to pass to gunicorn.", + dest="waitress_options", + default="--threads=4", + help="Specifies extra options to pass to waitress-serve.", ) def get_commands(self, options): - ret = [] - extra_options = options.get("gunicorn_options", "") + # Honor $PORT (e.g. Heroku) but default to 8000. + port = os.getenv("PORT") or "8000" + extra_options = options.get("waitress_options", "") command_line = ( - "gunicorn pykeg.web.wsgi:application " - "--config=python:pykeg.web.gunicorn_conf " - f"{extra_options}" + f"waitress-serve --host=0.0.0.0 --port={port} " + f"{extra_options} pykeg.web.wsgi:application" ).strip() - ret.append(("guincorn", command_line)) - return ret + return [("waitress", command_line)] diff --git a/pykeg/settings.py b/pykeg/settings.py index b32f3d85..3ab32454 100644 --- a/pykeg/settings.py +++ b/pykeg/settings.py @@ -49,7 +49,6 @@ "crispy_forms", "crispy_bootstrap4", "imagekit", - "gunicorn", "corsheaders", "rest_framework", "django_rq", diff --git a/pykeg/web/gunicorn_conf.py b/pykeg/web/gunicorn_conf.py deleted file mode 100644 index 14dbe209..00000000 --- a/pykeg/web/gunicorn_conf.py +++ /dev/null @@ -1,11 +0,0 @@ -import multiprocessing -import os - -if os.getenv("KEGBOT_IN_HEROKU") and os.getenv("PORT"): - # Necessary on Heroku, which doesn't respect EXPOSE :-\ - bind = "0.0.0.0:{}".format(int(os.getenv("PORT"))) -else: - bind = "0.0.0.0:8000" - -worker_class = "gevent" -workers = multiprocessing.cpu_count() * 2 + 1 diff --git a/pyproject.toml b/pyproject.toml index 97d12dc2..03270026 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,6 @@ dependencies = [ "fakeredis", "freezegun>=1.2.1,<2", "future>=0.18.2,<0.19", - "gevent", - "gunicorn", "httplib2", "isodate", "mysqlclient", @@ -38,6 +36,7 @@ dependencies = [ "requests", "requests-oauthlib", "rq>=2,<3", + "waitress>=3,<4", "whitenoise", ] diff --git a/uv.lock b/uv.lock index 34c2264b..cef72e55 100644 --- a/uv.lock +++ b/uv.lock @@ -41,46 +41,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] -[[package]] -name = "cffi" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, - { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, - { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, - { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, - { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, - { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, - { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, - { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, - { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, - { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, - { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, - { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, - { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, - { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, - { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, - { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, - { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, - { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, -] - [[package]] name = "cfgv" version = "3.5.0" @@ -444,163 +404,6 @@ version = "0.18.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8f/2e/cf6accf7415237d6faeeebdc7832023c90e0282aa16fd3263db0eb4715ec/future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307", size = 840896, upload-time = "2023-01-13T03:15:17.607Z" } -[[package]] -name = "gevent" -version = "26.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, - { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, - { name = "zope-event" }, - { name = "zope-interface" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/5c/92002455a57cb3634383e2b822e3bccf409f43cde34528e46428971475cf/gevent-26.7.0.tar.gz", hash = "sha256:5b333a556e38a302b1b8c80525bef16d437e16f1e7767947789406841856a102", size = 6729213, upload-time = "2026-07-22T20:16:04.713Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/60/878d0cdef05d952ac7f17ffe385143fb0f3720afce0f6ff5ddbf7aac0342/gevent-26.7.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:80e98fc808bd9cc5c911d78a443d214bf0c8f96c9fdd296893df7e40364d5f37", size = 2198781, upload-time = "2026-07-22T16:48:28.588Z" }, - { url = "https://files.pythonhosted.org/packages/69/79/6ce781b60049060e9d89b3d0fe60940353adeb39856aaad5ee925fd127e9/gevent-26.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf4b946b47cc6fdbdf9221f891db9a44df92166435c027760ee7dbdfb4039adc", size = 2229318, upload-time = "2026-07-22T17:02:11.713Z" }, - { url = "https://files.pythonhosted.org/packages/6d/38/48898f35c2092d699b755b01918551358db72b554c63f553c8f027d2bf31/gevent-26.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:55ce0b7f87f9befcc788d77eb039b1de89a35f37afc31942e12c7ae090a563b8", size = 1700480, upload-time = "2026-07-22T16:26:16.794Z" }, - { url = "https://files.pythonhosted.org/packages/93/51/53370896942523c333699394ccad379d186648dbfb913f42ce094ddfb4b3/gevent-26.7.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7f7143823ef99bc657534a2b6e8cbadedc910750cc0b4f4b4438a58d9fe43ab2", size = 1783048, upload-time = "2026-07-22T18:11:25.996Z" }, - { url = "https://files.pythonhosted.org/packages/ba/56/5a2cb36d75d3b626d6ffa116673b34442bf5afd6f7ab4b98e512c1b008d6/gevent-26.7.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:ca4019899830471910129968251c795c8aee59e225fd16326ae01c1f93f3cfa6", size = 1880257, upload-time = "2026-07-22T18:10:40.919Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ed/ee7eb2f03a38a4f33b0f327bab16d3158b3a794588a84dba739cbf4a3e68/gevent-26.7.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e4042da317a96d12110831cc404855f0c501a5a5aa476a7a18c3b480a5a59233", size = 1819378, upload-time = "2026-07-22T18:29:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/5e/aa/e2a202c03cff4f49bba54cc321c3afc29eee9d6fc452f4aa94d2f00e7d3d/gevent-26.7.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:5c97ca98e1aae427a267eae0fbfe8d0884327e6b1cd51fc2ef6642b8b0b82701", size = 2136837, upload-time = "2026-07-22T16:48:29.939Z" }, - { url = "https://files.pythonhosted.org/packages/ac/64/4892fbca47aa4e06b86aef96d6146bd61175b23b7db431ec7937d73b2f72/gevent-26.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5d5d1864bc3db92d1f82d1790395eda99f98b47fd9f7ec02c4e182d7828a8251", size = 1794058, upload-time = "2026-07-22T18:07:10.644Z" }, - { url = "https://files.pythonhosted.org/packages/21/23/90bb7d0c6f59d2973bb8f4bd3164be00e466823dea01568e0cb36f2afb77/gevent-26.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15fd2d88ed5370f8084079758758df91f26d2f68575e1ee76fce604ddba83e5e", size = 2159797, upload-time = "2026-07-22T17:02:13.229Z" }, - { url = "https://files.pythonhosted.org/packages/a3/67/4d1e315ee3052530fa8537e0cdf1f9c3a6606740f7372f420c7d12d5cf82/gevent-26.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:514bda3fff741d7e5ab108ee1d31550a7f4b2fd3dc6e3b6f38dfb8685efdafaa", size = 1682414, upload-time = "2026-07-22T16:26:22.18Z" }, - { url = "https://files.pythonhosted.org/packages/02/b1/d1b1de89677ee39e641ad8501ed72c2b99f1ddba1c14c462675f40b37a66/gevent-26.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:0f26f9a8c32ac0a73f6084c59b63deeacb350e7f1fee5301d95c5e0683a390d4", size = 1562794, upload-time = "2026-07-22T16:27:53.205Z" }, - { url = "https://files.pythonhosted.org/packages/2b/66/104590ad3a9e671b3ef77ad19c1cc50e7f1c8c220b27ddfaf34e5f88bd9c/gevent-26.7.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:92f256285fb43a57f152bd2e51a59cde1cd0b20869ae1e6da583b6beab88ed8a", size = 2953977, upload-time = "2026-07-22T16:23:41.654Z" }, - { url = "https://files.pythonhosted.org/packages/64/07/350d87161378633714184828bdc57c66f9b525eca5249ad1294dc7f8cf58/gevent-26.7.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0e4fea187c5df7168b9538b4f543fcb0fcbaeb93be3d6cd499c324652c740704", size = 1800960, upload-time = "2026-07-22T18:11:27.242Z" }, - { url = "https://files.pythonhosted.org/packages/c4/6c/ddfe298c2ecb1cfc72a03dd69ba751759f7e7835d3135f171b284eb09ed1/gevent-26.7.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:ce732fe08d0ea65de07eff6e46bade8ac6a6fdb65cc748c713f3d31ae122529e", size = 1900387, upload-time = "2026-07-22T18:10:42.973Z" }, - { url = "https://files.pythonhosted.org/packages/f9/89/2647bbbf1da35a1c271a54452e76065308cbaf684ee32a79f989ca4265f6/gevent-26.7.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:c25b3522072137aecf3389031039230190038f888e257f490b3897d0e0620f74", size = 1848046, upload-time = "2026-07-22T18:29:08.074Z" }, - { url = "https://files.pythonhosted.org/packages/31/52/4f9b4c536b5a0424e328d5a5466640d185020f1f0b08b2de0ca939cc0a0b/gevent-26.7.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:eaaa75c9014df3f8c310c64f53f1152af8c6be32e82734396bed91e1d0e6f35c", size = 2132200, upload-time = "2026-07-22T16:48:31.203Z" }, - { url = "https://files.pythonhosted.org/packages/42/88/1daffa63b257c381df68018e4d3da72b429955cf95a171a46d3220422c8d/gevent-26.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f1956032a9926ac9b4152b2a50bc5a2cc020722ec16928ccaf32e227ee0aae47", size = 1814237, upload-time = "2026-07-22T18:07:12.438Z" }, - { url = "https://files.pythonhosted.org/packages/73/4c/b996cdddca78eac435195cd97dff590c65a9e0052640bcb6f8b6f1e30b1d/gevent-26.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d989a1ad6cc54f5c69bb7304360f98b4fda80da2b773f1047db9fba61ae7379a", size = 2157938, upload-time = "2026-07-22T17:02:14.887Z" }, - { url = "https://files.pythonhosted.org/packages/08/fd/44419d7559a95e238ee45d29df30bad78a91d343cb27b13a6af552b907bb/gevent-26.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:e0c9ce2d80fc0f8894d748a1045ff26ad188e294bad656b29839271800827c85", size = 1685319, upload-time = "2026-07-22T16:26:13.954Z" }, - { url = "https://files.pythonhosted.org/packages/6f/7a/7df1762ccd9a40ce1ca626f50cb7a75758f2c930aabcc73c91cf00cb3448/gevent-26.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:959effe0c56cdee0bf761e5c4e78ab62880be147a2f2aa31112ca2f7e5754e53", size = 1558945, upload-time = "2026-07-22T16:26:56.737Z" }, - { url = "https://files.pythonhosted.org/packages/75/63/0fcfbe3f5696e56424f331ec41e0e447cea79c384848d6019e3f7f340f4b/gevent-26.7.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b1b89eb5566f75aa8b2bbdb0308e1ac8d9113ca7cff85b45366aea9faad639a1", size = 2976844, upload-time = "2026-07-22T16:24:39.275Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6c/ea2d0afbe760c18df5bd1631dbe5a73d840d9b141cb71e6810157c2ae28a/gevent-26.7.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:449857ce058183442e2d71d83ff0c587a3ddff631e93c6d19a6dffb4814eccad", size = 1802332, upload-time = "2026-07-22T18:11:29.155Z" }, - { url = "https://files.pythonhosted.org/packages/59/90/36f2258f1bfe8601224f6159066103b832c31e1451ce8dc2cd2408b6ecf4/gevent-26.7.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:8260a3f38b05fcf3c283417b18617562dbec74f5784f748e4ba3866789d7f3a4", size = 1901253, upload-time = "2026-07-22T18:10:44.402Z" }, - { url = "https://files.pythonhosted.org/packages/dd/38/86dd67e5c2dfab016a9c935b4338d6cf8f9bfa72dc5a0f3fb879d993127a/gevent-26.7.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:30894398d06747b433c8923a6a77ede61259ce6822a99f6c6e7fa0216ccb73c3", size = 1850489, upload-time = "2026-07-22T18:29:09.694Z" }, - { url = "https://files.pythonhosted.org/packages/f1/33/f5651942a5967483298b6ce6f45572d33120dd0fd01c8991d3fca5b1e8ee/gevent-26.7.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0b753522498118c9489753de7c612d4baed0edf384d9df2bf9492233ba1c20ff", size = 2129813, upload-time = "2026-07-22T16:48:32.551Z" }, - { url = "https://files.pythonhosted.org/packages/c0/09/abe8217a8fcd3f0e94c9eec024a5499c0f267cb269ccea6c0e2e812319b9/gevent-26.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:055a643026dc28daff2be228555a2097937448cc9b58307edebcf81b9d78ff4b", size = 1815121, upload-time = "2026-07-22T18:07:13.988Z" }, - { url = "https://files.pythonhosted.org/packages/40/d6/dbae1cd2d27b62664cefa086035530eb21203d45b12f466272441c048c9c/gevent-26.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e1dc6a2712de67fd210e1f1a408601f6908b042f6420e188106f2f37f94ec71", size = 2155913, upload-time = "2026-07-22T17:02:16.35Z" }, - { url = "https://files.pythonhosted.org/packages/fc/42/90b662f4eb27d7727d4619d5c6be872117f1a9f187b243ec7f6fef988ce7/gevent-26.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:44e5280296129c0915addaefdb37d6e9bc124a77a433b1b1c8ddf1853c53f4e7", size = 1682483, upload-time = "2026-07-22T16:26:30.492Z" }, - { url = "https://files.pythonhosted.org/packages/e1/84/7297c56b9fff463c4ba2f685dbb913a855df903046dc68d14e8655a29ffe/gevent-26.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f08b1aa6729f794409ca137e25f671e0d9bbda4451200c5e28a769375365388", size = 1556053, upload-time = "2026-07-22T16:26:14.365Z" }, - { url = "https://files.pythonhosted.org/packages/c1/bb/ab60d496cbdc0293ebbd6c2070b34da0632bd7a2ca20163c17e18d2d2dc9/gevent-26.7.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:0e0e3bf7ae0f82dbc5c6be26b4781e86c97f1e28d516b7a9746ac8b04bcc6948", size = 2992503, upload-time = "2026-07-22T16:24:36.503Z" }, - { url = "https://files.pythonhosted.org/packages/5c/35/75f27c06a82a5b22600aaccbd9567d89bb4091be43e96c02981f10aff23d/gevent-26.7.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:740050b53048207b080a1e183a377c47809ad0b7b7b0cd7eab0dea1045f7e480", size = 1809173, upload-time = "2026-07-22T18:11:30.724Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5f/a6b32b4db3fa76bd8a070f0f46f5306123bf6336e7a0ca0cd2f9b99473df/gevent-26.7.0-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:67983607eb6c7bafa362c5c43b69a27145b936c34a3d6441ed42413d62fae0a6", size = 1906630, upload-time = "2026-07-22T18:10:45.836Z" }, - { url = "https://files.pythonhosted.org/packages/e1/87/832495d8fcc05ff7432f038b7c4decbd5632425a2cd5da2ce73cb2d800c4/gevent-26.7.0-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:475848518d708e07d1987c3d94cb8ff53e2b3a69df32e39feda2779cafe400b0", size = 1855278, upload-time = "2026-07-22T18:29:11.517Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/2f36c0fa389fa2b7ceb5a8972b0e7da7bc770f9135315cf4246c607ca5fc/gevent-26.7.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f8ed457dd616bfe6682569f92730f9ab45aafb1aeca5e80eb2f6b9a2ce26d11", size = 2136155, upload-time = "2026-07-22T16:48:33.865Z" }, - { url = "https://files.pythonhosted.org/packages/b5/98/09f2cfaa23dbce48e3271e95b0d003f93acece6b5cfd40f4cebe3850d79b/gevent-26.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15373c68cf1fa14114bec2f09b16e2c65374bd5309e897e0a28740b09ce329e0", size = 1822108, upload-time = "2026-07-22T18:07:15.397Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d8/05a294165c17569f04284ad3c889684c8780544885b4cdf77b1432947d0c/gevent-26.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:73f3d53f2f390369e290c933b75bd87f1f2261f2f2f2175aa667c43ee3049bad", size = 2162814, upload-time = "2026-07-22T17:02:18.066Z" }, - { url = "https://files.pythonhosted.org/packages/59/89/58a545c4eda33e106d6887a0387adc2249abc14c779e3eb88bbfdf3768d6/gevent-26.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:f11b558d544ad2249029ba023cd6519ec3a0eee54a3d027e6515c1eaa322422a", size = 1706971, upload-time = "2026-07-22T16:27:02.263Z" }, - { url = "https://files.pythonhosted.org/packages/bb/cd/413f293e54961e5c89c54235370e3603ec0f561e7ace8357980410efbf78/gevent-26.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:3871f4ca59ec2328c3ef638a0fe01a28a825443a133368dc78eb5ceadcad7609", size = 1585078, upload-time = "2026-07-22T16:30:48.145Z" }, - { url = "https://files.pythonhosted.org/packages/21/3a/47f29f632aaa38aa12410f57f1732fc50bfd4d4006d2e7e022ce731cabc9/gevent-26.7.0-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:3e3d6e20a94239ad353b776e72b8ce18c35dbe4e98c279aef3932651553d8404", size = 2996208, upload-time = "2026-07-22T16:23:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b3/4620f1ce81ecec9890229806c73f07dd022e40f76a552bd430391e7316c4/gevent-26.7.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:ddbd3cc76b9bc69df651a216c2a62fc6415ad463b3ac9c6cbbbb8b7b8224af17", size = 1811545, upload-time = "2026-07-22T18:11:32.224Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/d285212ffd5585d511299e13e61d76262def8e826e9f20c92cb85df406f2/gevent-26.7.0-cp315-cp315-manylinux_2_28_ppc64le.whl", hash = "sha256:01ceab7e608dc1b9859d9511a0a29d7ce2e7d909ab19fddc860e70a2ed5b10ce", size = 1910418, upload-time = "2026-07-22T18:10:47.709Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e0/c5d666e6065652918cfb6e6a3cf8f721d0e152c57cec217ad81792a1323b/gevent-26.7.0-cp315-cp315-manylinux_2_28_s390x.whl", hash = "sha256:2e6c917b2b8baeb6080797a6b25e35e1fd784319a05bb92b87c53546e5578eb2", size = 1857891, upload-time = "2026-07-22T18:29:13.13Z" }, - { url = "https://files.pythonhosted.org/packages/a1/67/e945ed458fa98b34572876bfd0d35fe4fa3f1159f43660b71d982b7cb63e/gevent-26.7.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:df75a1748b26030f2f7f10042cc45640b22954d9d0dc6b4b6f0dbe0b6751a2d4", size = 2138121, upload-time = "2026-07-22T16:48:35.358Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/622468fa1a3c4cf51f20e14813f5cc1592fe6e44a42ccca9195a0b18c769/gevent-26.7.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:ee1b389587e5d5c1eb19d0455b5b4d7a0fb5c5287af4e226ec66d9dfd2548107", size = 1825114, upload-time = "2026-07-22T18:07:16.667Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7a/151a2afcacf487ca25faf8b1bdd6c5b4ace2f7c1e6b4eaffe0a5e6e1df61/gevent-26.7.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c2918641ba756f46aa01ab9dd82d6dfceec403c77c2787298746b411dcf0288e", size = 2165990, upload-time = "2026-07-22T17:02:19.817Z" }, -] - -[[package]] -name = "greenlet" -version = "3.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/74/b13368064b09053253555d3f2839cc2684d22d5aed0d2ccffbf7a6736558/greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20", size = 206538, upload-time = "2026-07-22T12:47:14.468Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/9d/58f80897f4121f5c218bb931cf6d3b6514873f02ad0b729f744352926b9f/greenlet-3.5.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ac5bf81d79d2c8eeb2ef6359b2e1687a1e9ebf46c2b1f970da9a9255df51d190", size = 293072, upload-time = "2026-07-22T11:38:14.299Z" }, - { url = "https://files.pythonhosted.org/packages/dd/9f/b4bc9bbd6a7855cbd8ad8a83c874eeeca56c24de9132b3323f81c03a30ba/greenlet-3.5.4-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89f3738167bab8c1084b94e23023d41d247117ac149fa0fbcb5bd4cf6262b353", size = 609393, upload-time = "2026-07-22T12:26:37.964Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/744b5e063af127d2e3c74fe0f1aef15573064c83b6066883524f5b258b17/greenlet-3.5.4-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9a5e3406e3ed8125ae1a3b37c12f3434e2b1f0fa053197c5557895b4fb09606", size = 622750, upload-time = "2026-07-22T12:28:59.546Z" }, - { url = "https://files.pythonhosted.org/packages/5d/5c/53d6b94742a6f1ee1877c7ff76262c909e137f3f7383ce96a8ab78e1ae31/greenlet-3.5.4-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a2d614cb2372c7101a12ea8b96dd56f81c986d247c5a73db67063f3ed1ca4a52", size = 629659, upload-time = "2026-07-22T12:43:40.451Z" }, - { url = "https://files.pythonhosted.org/packages/d6/6b/d78ea2908e8e08985348f28ac396c2950be7ab66321dfe0054c73bd1f456/greenlet-3.5.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ab9f0704bccf6d3b38e0d2130b7b33271cff11453690da074fa280c3aa8e8e7", size = 622920, upload-time = "2026-07-22T11:51:06.83Z" }, - { url = "https://files.pythonhosted.org/packages/f2/34/957fc5577180ef2f57be82580ee1f59fdefad4f6c623c7d5e1b6980a76fb/greenlet-3.5.4-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:188e4d142f243051d92a1f5c244a741da02dddc070a0620c842804d7b56d008c", size = 425580, upload-time = "2026-07-22T12:39:48.326Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e4/3ce7009c948920b01527f8d9da29f501a31ac3d98318829e981fd879b850/greenlet-3.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2cdaadc3d31445a8f782bde3cd37e49a2c2a9c6da6daf76a3e34c683b271a3c7", size = 1582262, upload-time = "2026-07-22T12:25:01.131Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4c/0408366102a33829f7bdd6a992dad75abbf75e86cc1e76caf19e57311d29/greenlet-3.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70bdfacdc183dac838b2a0aaff2dd6134a457c52fe68a9c6bbab435483d2b9df", size = 1648906, upload-time = "2026-07-22T11:51:08.627Z" }, - { url = "https://files.pythonhosted.org/packages/13/52/ebfe8f6a1aeb8e430540b406c844ecc4e3367072b0192f69dcb85eeeec2b/greenlet-3.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:69173331fbc5d64bfac0065d7e22c39cfcd089e9b18d125bdcd5079363b09616", size = 246036, upload-time = "2026-07-22T11:38:30.073Z" }, - { url = "https://files.pythonhosted.org/packages/61/16/71eefcf68267bbf06a9b6bff57d0b222e49432326e85d74348b67694b8d4/greenlet-3.5.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e883de250e299654b1f1680f72a1a9f9ba62c9bd1bce84099c90657349a8dfbb", size = 294266, upload-time = "2026-07-22T11:37:56.142Z" }, - { url = "https://files.pythonhosted.org/packages/36/ea/a0b19adfc35d07e10acb626e9d22a3893b95f1309c42c4a20161dec16800/greenlet-3.5.4-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32802705c2c1ff25e8237b3bdacf2594fa02be80af8a66703eb7853ea7e68686", size = 613712, upload-time = "2026-07-22T12:26:39.375Z" }, - { url = "https://files.pythonhosted.org/packages/54/76/a121978b3337407d05a1ce5f79b4aa5998a43a9d8422f9726029b90b4471/greenlet-3.5.4-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57aa201b351f7c7c75627c60d29e4d5b97a07d37efeb62b903466fca42c097d7", size = 625582, upload-time = "2026-07-22T12:29:00.814Z" }, - { url = "https://files.pythonhosted.org/packages/d1/4a/f301f1d85c69a86b90b5d581a73e8927bba4e79450037e6e2cbca05eb4fd/greenlet-3.5.4-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9667862a2e38ad379f11b845daeda22c8989186def44f06962c9c4c05e556da7", size = 633429, upload-time = "2026-07-22T12:43:42.073Z" }, - { url = "https://files.pythonhosted.org/packages/34/c2/080f16cf870e929e592f55767f01d6c98d2ee83bfdc36c3b892f2d0459ab/greenlet-3.5.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3fe76c2cac86b4f7a1e92865ac0a54384deb05c92986287c1a7110d9bd53071", size = 624663, upload-time = "2026-07-22T11:51:08.016Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2e/26884072b0eb343a4d5fee903341bfe5171b32b7f14553886e2b6349135a/greenlet-3.5.4-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:ae53534b5dec0f4c2ec26f898f538dc8ea1ca3ef2927d597a9439e40a09da937", size = 428238, upload-time = "2026-07-22T12:39:49.973Z" }, - { url = "https://files.pythonhosted.org/packages/9e/bb/8f3ca88370b817369008faeceeee85970adc16c92a70a3e5fe5fea495a57/greenlet-3.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e1a4a684b16c45ba324e60b32a4386a87722bcb815d2a149d2182f9b401ca72", size = 1585010, upload-time = "2026-07-22T12:25:02.539Z" }, - { url = "https://files.pythonhosted.org/packages/51/c2/45877154689709ebce9a0b83c2235e6ca0f31577889b02af308c8cc5f8fb/greenlet-3.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e849e6e139b9671adeac505f72fc05f4af7fd1921faef40295e214fc3b361b59", size = 1651283, upload-time = "2026-07-22T11:51:10.408Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7d/8711a75cb61d85246277c07ff6e1a6504621ba473d808c11ad225ffca43f/greenlet-3.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc418cf4c873357964d6624445ed09472e50def990c65dd4e76fc3ba8cd9cef6", size = 246434, upload-time = "2026-07-22T11:43:15.557Z" }, - { url = "https://files.pythonhosted.org/packages/00/62/e290b3bce433da8f0324ac02da0b128d683482229f1a8b789fa47818a4cd/greenlet-3.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:c38c902a0986eba1f6e7ba1ab39ad5195926abde90f3fe080e08212db62176da", size = 244990, upload-time = "2026-07-22T11:39:22.626Z" }, - { url = "https://files.pythonhosted.org/packages/f3/04/81bd731d6d1e3a469d9a4c36f5eb069bcf0cbb2d5d342c9fec22245b91fc/greenlet-3.5.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3d66250e8b09f182ede05490998c818b5961f7a3640332d44c4927caec7bbfe4", size = 295909, upload-time = "2026-07-22T11:38:09.261Z" }, - { url = "https://files.pythonhosted.org/packages/cc/dd/f5f22903a6ae70f5ea328ed0beaec92ad903f0e3b7d2845133b354abc4b8/greenlet-3.5.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c90e930c9c192e5b3ee9fb8bcd920ea3926155e2e3ded39fc697323addecee17", size = 612011, upload-time = "2026-07-22T12:26:40.69Z" }, - { url = "https://files.pythonhosted.org/packages/8e/10/92a4a88d12b915d74ea5b6d288e4afefda4771647caa34442c156f7a454f/greenlet-3.5.4-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:791fdfeeb9c6e0c7b10fa151bf110d2a6974866f13dcb5b1c7efae698245893a", size = 624299, upload-time = "2026-07-22T12:29:02.089Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f9/03e26be3487c5238e81f2b84714959a86ea8515a869828cf41f4fc54b34e/greenlet-3.5.4-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b7c895310363f310361e0fe2072af85269d2a2a285cd04c0c59e79a5e3670dcf", size = 629603, upload-time = "2026-07-22T12:43:43.456Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/0b14bb9db2989f32cd9fe7f76afedea01ee8bee3f87c07e69f24adfe7e63/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f88193799d43dbf8c8a806d6405c9c52fe2af40bf75072a606357b33cc336c7f", size = 621541, upload-time = "2026-07-22T11:51:09.464Z" }, - { url = "https://files.pythonhosted.org/packages/57/6b/7c55ca72ef80d57c16c4a55210f82582622462dc4485799a30f4ec6f3372/greenlet-3.5.4-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:13b980043cb1b3134e81ea469da1250ddcc6bfe6d245bbaa59168d9cdc8f228f", size = 432554, upload-time = "2026-07-22T12:39:51.379Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/25e9a2d9eb6b2e8b7ca4e80a3a26cb887cce6c8e0a87c921164f11bc5574/greenlet-3.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7a5f095767c4493afcd06067f2bb3b8716e3f3f9e92b99c88e7e99f885b3d4d", size = 1581444, upload-time = "2026-07-22T12:25:03.818Z" }, - { url = "https://files.pythonhosted.org/packages/b9/96/4c9bf2e2c408dcc0556edce69efa9f802e82223573c53240136a086821f1/greenlet-3.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:42afdc1ab5f66da8c586c32af9224a74a706b4f0ea0dc3a4188a0860a09c65c9", size = 1645842, upload-time = "2026-07-22T11:51:12.295Z" }, - { url = "https://files.pythonhosted.org/packages/b5/41/303ecb26a3a56122c0f4d4073ee078881847bd6b6f463ae0ec57ec20223b/greenlet-3.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:60149df8f462d1b230038e6590c23c3b4768bb5d6c022b3b6e82532b34b0b8a3", size = 247169, upload-time = "2026-07-22T11:38:19.893Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e3/ef56864b4c35fcb3eb3b41b869f6cc46f4cd3f5e2c68e74acde8ac433951/greenlet-3.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:77d6ce04fed0d9aeed42e0f37923cc43eba9b027bdd9c34546bb4ccd143d0fe0", size = 245565, upload-time = "2026-07-22T11:38:27.061Z" }, - { url = "https://files.pythonhosted.org/packages/c0/9a/e51225dcd58713f16ccbdcc501a8da21098ea14515b7870f1f94459e5ff5/greenlet-3.5.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:24e61b88cb7e1b1d794b32a10cc346ac779681d6d74ff137a3e0a444d2bf1f02", size = 294831, upload-time = "2026-07-22T11:38:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ea/de50a50fadf979713ab18b46f22ad5ff5f2dcfc637a3ebdecf669801e1a5/greenlet-3.5.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:870d730fec833f5a06906a32596cc099b9161594642a92a520b7a88911c95356", size = 614619, upload-time = "2026-07-22T12:26:42.282Z" }, - { url = "https://files.pythonhosted.org/packages/db/c7/2aae27fea41205b8650294c301f042a2a4bb6155eea48c995b890a92f2c1/greenlet-3.5.4-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec5ff0d1878df6af3bf9b638a5a92a7d5693291de77c91bff10fa48519c604ef", size = 627021, upload-time = "2026-07-22T12:29:03.445Z" }, - { url = "https://files.pythonhosted.org/packages/1b/80/fb4d4788bbc8e54761f1fc88533af9523a6e86299fa113d6e8a8503ed9fc/greenlet-3.5.4-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07bd44616608d873d06735b63ef1a88191d6ca57c8d291d6559c71bc14c0893c", size = 632845, upload-time = "2026-07-22T12:43:45.19Z" }, - { url = "https://files.pythonhosted.org/packages/eb/56/79fd826f9ccaae0b84e1b4ef68dabba5e105bb044ffcd448a0b782fcba9a/greenlet-3.5.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d84d993f6e575c950d91a23c1345d18fe1a4310d447bf630849d7809196b52f0", size = 624002, upload-time = "2026-07-22T11:51:11.391Z" }, - { url = "https://files.pythonhosted.org/packages/42/e3/6086fa578ebb72772722cdc4bcd628459814b42e0c2db1e3cbd6552b3271/greenlet-3.5.4-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:3529a8a933582ad19e224792cac7372489526576b75b4c124e8e4f29948f4861", size = 435053, upload-time = "2026-07-22T12:39:52.715Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1a/27319f97e731298513dcba1a2e91b63e9d8811d9de22130f960b129b1bf1/greenlet-3.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58023945f421093de5e6fa108c0985a8659d43f49e0216da25099369a121bcbd", size = 1581533, upload-time = "2026-07-22T12:25:05.322Z" }, - { url = "https://files.pythonhosted.org/packages/b1/6d/24240bf562e9786dd2799ee0a4a4dadb4ded22510f41b20245099159ac8c/greenlet-3.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bae2728e1897aa8df8cb1af38cd48b3a743aefe29372de7b8b7a9f532501e69f", size = 1645781, upload-time = "2026-07-22T11:51:14.805Z" }, - { url = "https://files.pythonhosted.org/packages/c1/5a/442ab1a9ef7ca6bf7210e5397a95972206a91a31033a03c8900866a10039/greenlet-3.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:ca5726c0b08ca35ae873557266a78b2c3f3b2b7d7401aa5ff886c2045dd0111c", size = 247133, upload-time = "2026-07-22T11:39:20.661Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e6/9160210222386b1a378ff94db846b9508ca24a121cf684991561fdb69280/greenlet-3.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:7c1303791d603080cac6fc3b34df51c3b75b723739c282c8029e48a0d241672f", size = 245500, upload-time = "2026-07-22T11:40:22.185Z" }, - { url = "https://files.pythonhosted.org/packages/a5/a7/6ab1d4f9cd548d15ab90da29947f2076100130bb179b0bde59f795a459e3/greenlet-3.5.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7e8afa5eac028f8140ceafe5ceec66e6aa127ddcb21452d2a564dcd2900b5f22", size = 295410, upload-time = "2026-07-22T11:40:35.747Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7a/422f63b4715cbc0b24385305407adf38b48f6bb68b3e6b04090e994d0f5a/greenlet-3.5.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73b37afe369021423ea53dd3123e04bffa7e93ac64429b9f50835b2e4fcae7cf", size = 661286, upload-time = "2026-07-22T12:26:43.8Z" }, - { url = "https://files.pythonhosted.org/packages/d0/31/5a1cac663bf5582190c5a714ef81364f03cde232227f39748f8ae4c11da5/greenlet-3.5.4-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ef964f56dfcb6f9bbef2a190d9126795eac408716aeae47b5e7c73c32aafca9", size = 673517, upload-time = "2026-07-22T12:29:04.815Z" }, - { url = "https://files.pythonhosted.org/packages/9c/bf/250c2921c7b585dde12f5239e313ca2dcbc464d161ecca36e4e6ef21762d/greenlet-3.5.4-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cef589bc65fae02d10bca2ac341191c5b33acc2967892ebf4fcbd10eabb7a74c", size = 677968, upload-time = "2026-07-22T12:43:46.788Z" }, - { url = "https://files.pythonhosted.org/packages/15/4a/2a82a1e3f8aaca020853ac8d12211280ca2b231aa08ea39f636f1060c319/greenlet-3.5.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c53ff01a5c53a40f2c16820ebc56d7c61a77f5fbe009dadd96292d5682f80f8", size = 670917, upload-time = "2026-07-22T11:51:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/18/40/10bfcf6513558d82f7b95dd728001c63bd388259fe27d3e30ae01f103430/greenlet-3.5.4-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:dfc41ae893d9ceaf22c824f2153a88b30651b20e8758c2cd9ac143f23640563c", size = 480643, upload-time = "2026-07-22T12:39:54.149Z" }, - { url = "https://files.pythonhosted.org/packages/68/b0/e379a152b17bfdfa95795af4049e37c0fd1b4d81f020d426db104ed07c77/greenlet-3.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecca4d80d55a01ad6b23b33262662956149fbb7b2c6be2910f1705921958cbf3", size = 1628478, upload-time = "2026-07-22T12:25:06.678Z" }, - { url = "https://files.pythonhosted.org/packages/5e/43/bffdfa64f7317f954c5c1230b5dd5922676ce198689a68c1ac1ed4b1b1a5/greenlet-3.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffbc533e0eaf8e80d8471411646ab88fe58f641d508c0b02b24494479f4d9ec", size = 1692021, upload-time = "2026-07-22T11:51:17.008Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/f799f9637e2c6e9b0b716015e339040598b058cf7654dfc0d67468b177ed/greenlet-3.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:305f69e6c4523d7f6979ed001cff4e5853c063e5da04880296603aa0227e544c", size = 248031, upload-time = "2026-07-22T11:40:11.007Z" }, - { url = "https://files.pythonhosted.org/packages/05/75/625bcdd74d5e6b2dca1ecba3c3ac77bcf8a026c21a649a46cef23e421f97/greenlet-3.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:f260930bbbbcf9caee661211235a5111c86dfe5832fdf6ae4570da1e0995320f", size = 246892, upload-time = "2026-07-22T11:40:27.357Z" }, - { url = "https://files.pythonhosted.org/packages/ec/69/35c62ed49c320cb4d98e14698ccca5467d3bfe683984172be9cb564d9ce3/greenlet-3.5.4-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:41ddab54e4b238f4a6c323f39b4e59e176affd5a94d461a9fb7583dac74240a3", size = 305571, upload-time = "2026-07-22T11:40:31.659Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6c/64d60216b3640dcb0b62d913dd9e0d80030c09115bb2e4ba70c95d10ca45/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3dabe3e2809013052c68bdf0b7fa5f5f2859c43a80803131ad61af9cabd7867", size = 672568, upload-time = "2026-07-22T12:26:45.298Z" }, - { url = "https://files.pythonhosted.org/packages/5c/de/ba3ab0a96292e53039530333b0d2ae18d9e508f3a325cd7bf15f8172944c/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:27d3f00718634d4520a3a150154ac5da36f257869d41321953375b90bfbbc72c", size = 680076, upload-time = "2026-07-22T12:29:06.125Z" }, - { url = "https://files.pythonhosted.org/packages/ae/db/24a10af12bf8e639cec46c38b9ce1a282543ba42ff4fb0b31a970f1ab603/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39169a11d87a6a263afda3e9a27d1df16d0f919d40a4837cc73986c9884c0dd8", size = 681690, upload-time = "2026-07-22T12:43:48.109Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/aeada79083c6f1c15f45d77a332f9c441af263ee298e3eb17522cd337d22/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbd60b5763c6543c1827e48faaf14ea9bfbad245f52b1a4d76a2a2d8884c6c66", size = 676733, upload-time = "2026-07-22T11:51:16.027Z" }, - { url = "https://files.pythonhosted.org/packages/f4/60/44a2eca7b9fd71ae0fae7ff184da1cd3169d176652b97aa1cffcbb0ef961/greenlet-3.5.4-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:bd3d1145f603b2db19feb9078c2e6855eb7c67e15580c010ed815cee519b86fd", size = 510263, upload-time = "2026-07-22T12:39:55.678Z" }, - { url = "https://files.pythonhosted.org/packages/e9/10/2392fc3a98948652ef5fd1e7275c04f861dd13f74b78a2b4309f4ee4d090/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f00f910f0e7b35416c63b23ad78b769aeccfc1775f712b43c4ee525624a2eef7", size = 1637327, upload-time = "2026-07-22T12:25:07.879Z" }, - { url = "https://files.pythonhosted.org/packages/55/c6/e7237a3dfa1f205ed0d9ea1e46d70bd2811b32d516266399fb59d28ab90a/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:91c26423753b92caf41ab3f98fd547d7374d4d9fc2d85be041886c1579d9255e", size = 1697493, upload-time = "2026-07-22T11:51:19.214Z" }, - { url = "https://files.pythonhosted.org/packages/55/e3/4ba8154ba2a3d43729e499f72471b4b5c993f3826d3e24da81d5f06d6572/greenlet-3.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:ee032b91fd8ec29ec6c4cea2b8c561b178435134bd0752c7334b94e9c736c132", size = 251637, upload-time = "2026-07-22T11:40:37.44Z" }, - { url = "https://files.pythonhosted.org/packages/90/03/e3f96dfc100261a29545ddc8270cafe58f9195b6651466910e820910de77/greenlet-3.5.4-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:178111881dd7a6c946471fda85485ec796e1043c2b939f694b096e2ecf986809", size = 296076, upload-time = "2026-07-22T11:39:38.364Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3d/da52d208e5c977bce8667e784729e584e38b5785f4c1ec0f4c836e9a1c42/greenlet-3.5.4-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d92df08dd65fede97fc37aad36c2e9dcda3b31c467f8e0c2c096456cb818e927", size = 666870, upload-time = "2026-07-22T12:26:46.691Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8a/7e6dee25cb8a8cf9b362c8e597cc269593378bd916f16c736c059a52e85a/greenlet-3.5.4-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99e8f8c4ebc4fd80aa26c1280ae9ad43a0976e786349703a181cf0bae60413e5", size = 677678, upload-time = "2026-07-22T12:29:07.508Z" }, - { url = "https://files.pythonhosted.org/packages/51/a7/dafc7415d430b0a43a16396eb49ecb3b62fd720877fb259cc4dcfaf5f31e/greenlet-3.5.4-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f17e362d78e37559e0506c5a7d066bdd45073c36a0127a543e8a0df27242ff3", size = 681428, upload-time = "2026-07-22T12:43:49.623Z" }, - { url = "https://files.pythonhosted.org/packages/6c/21/5a38699fa45de749e3857d93b8f07e4c20489e77c2d35d915a2e1c456606/greenlet-3.5.4-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:394de08dad5ffcb1f50c2159d93e398d9d2da3ed437645eaa54771fa720db9f0", size = 676067, upload-time = "2026-07-22T11:51:18.163Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d9/6298f3432de301d4718766cf934bd73c418c73f81fbb77247319364b0d96/greenlet-3.5.4-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:cd320d998cbaa032932830448e39abf3c6a12901295e386e8114db926e10cffb", size = 487446, upload-time = "2026-07-22T12:39:57.044Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ba/863116ab8ff1ca7a729e327800268939d182db47aa433db70e216e7d9194/greenlet-3.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:c883d61f2282d72c767a14936641b3efcbde9d82f1080712aaea0b1d3126cb88", size = 1633489, upload-time = "2026-07-22T12:25:09.605Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/7466ced82818d6132462d7f26b3f83c66ea15d2b193a6c0088d558ed7d95/greenlet-3.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2a924f15d17957e252a810acefcb5942f5ca712298e8b6fcaed9a307d357522c", size = 1696584, upload-time = "2026-07-22T11:51:21.304Z" }, - { url = "https://files.pythonhosted.org/packages/f9/4d/55b638489260065de9ffce606c8b5d04507bef705de4b212a0c3d6a1a0df/greenlet-3.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ed17e5f3420360d5b459de8462efb52060399a5326a613d4cde31cef63ef95da", size = 248297, upload-time = "2026-07-22T11:42:04.055Z" }, - { url = "https://files.pythonhosted.org/packages/bb/08/9dd4ae635da93d41dc268bc34bd62a9d711ed8b8825c5d22ac910c7d6e6d/greenlet-3.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:f908898d6fa484ce4b6f447ce70ea99b52c503fee419e53cf74d60a16bc9e667", size = 247423, upload-time = "2026-07-22T11:44:00.764Z" }, - { url = "https://files.pythonhosted.org/packages/19/66/7c87ed9cdbf1d49c2c6cd1c7b9dd4d16c33b24235ca03972293a1876b30c/greenlet-3.5.4-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:1833637f17d5e7472548a48575c394fe39f1b1890d676d162d86593610f44d8c", size = 306487, upload-time = "2026-07-22T11:41:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/5b/05/0a4201e7c0054866eefc05da234f236dd4c950d0fbf9ca0517141f01b269/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12cda9122e03341f1cb6b8207a19d7a9d375e52f1b4e9243918375f40fd7b4b9", size = 676479, upload-time = "2026-07-22T12:26:48.129Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c0/4b6b8c5a3aec70f0649cd89662d120fdd6421e2bdc8e3b15c3ab5ec568d8/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d83ae0e32d14957ab7170785a20f582635c8474deab1bfbb552b17e769a6ce25", size = 684321, upload-time = "2026-07-22T12:29:08.925Z" }, - { url = "https://files.pythonhosted.org/packages/88/15/0b167aeea95285b0e654ddce651922f666c089363c2ec528ca8b9a9ba74f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:123aa379c962ed5fe90a880327e0c3066124ac64ec99e12a238be9fd8eb3db3d", size = 685995, upload-time = "2026-07-22T12:43:50.993Z" }, - { url = "https://files.pythonhosted.org/packages/24/c9/b49c31c9a972eee91e260445770e922244a7efc542697f51a012ec046d0f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f1467de1bb767f75db0aa34c195e3a496d8d1278c796e70c24ce205d3e99cde", size = 681293, upload-time = "2026-07-22T11:51:20.43Z" }, - { url = "https://files.pythonhosted.org/packages/de/90/c023ec337f32ff505be7db759c80d98f0532bb94d0c6fa13645efe9bee2e/greenlet-3.5.4-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:adf2244d7f69409925a8f22ed22cc5f93cdfe5c9dc87ff3476be2c2aaae61a05", size = 516928, upload-time = "2026-07-22T12:39:58.359Z" }, - { url = "https://files.pythonhosted.org/packages/95/6f/7f2d4653770500eee667866016d42d7a68e3d3462f80df6b8e3fcd48a0eb/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0fa53040b78b578120eecdc0265e3f1051487cc425d11a2b7c761daadf4feaa8", size = 1642474, upload-time = "2026-07-22T12:25:10.819Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d8/8cba31036a4caae448087ba5d150660ab03b4a0f54d9150f6495a3be7262/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:60e0bc961d367df506660e9ac0177a76bc6d81305300704b0977d1634f76efe2", size = 1701012, upload-time = "2026-07-22T11:51:23.17Z" }, - { url = "https://files.pythonhosted.org/packages/e3/cd/3f77a4cce3bae631b08eb52f53a82a976669600337e21dfdba811cb50267/greenlet-3.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f680e549edb3eaf21eea4e7fe101e15ec180c74b7879ab46adc080f22d4015d2", size = 251977, upload-time = "2026-07-22T11:41:38.125Z" }, - { url = "https://files.pythonhosted.org/packages/93/e8/65e8707d00fe2a49bf12f609a9b2b39ba6dd23c2810eacad877c4fc94bfe/greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994", size = 250538, upload-time = "2026-07-22T11:40:17.985Z" }, -] - -[[package]] -name = "gunicorn" -version = "26.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" }, -] - [[package]] name = "httplib2" version = "0.32.0" @@ -682,8 +485,6 @@ dependencies = [ { name = "fakeredis" }, { name = "freezegun" }, { name = "future" }, - { name = "gevent" }, - { name = "gunicorn" }, { name = "httplib2" }, { name = "isodate" }, { name = "mysqlclient" }, @@ -698,6 +499,7 @@ dependencies = [ { name = "requests" }, { name = "requests-oauthlib" }, { name = "rq" }, + { name = "waitress" }, { name = "whitenoise" }, ] @@ -729,8 +531,6 @@ requires-dist = [ { name = "fakeredis" }, { name = "freezegun", specifier = ">=1.2.1,<2" }, { name = "future", specifier = ">=0.18.2,<0.19" }, - { name = "gevent" }, - { name = "gunicorn" }, { name = "httplib2" }, { name = "isodate" }, { name = "mysqlclient" }, @@ -745,6 +545,7 @@ requires-dist = [ { name = "requests" }, { name = "requests-oauthlib" }, { name = "rq", specifier = ">=2,<3" }, + { name = "waitress", specifier = ">=3,<4" }, { name = "whitenoise" }, ] @@ -966,15 +767,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/4a/a3566f77501c21a6c2a1fc234dbe5dff74a86e3f150ef070e4ffb835e7f9/psycopg2-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:a73d5513bfe929c56555006c7a9cc7ae6e4276aa99dd2b1e2544eb8bb54f8b23", size = 2848588, upload-time = "2026-04-20T23:33:25.983Z" }, ] -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -1354,6 +1146,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4b/a7/ded126c19495158a05c7202b3389139839d4cf78d622d453867778e0f7a8/virtualenv-21.7.1-py3-none-any.whl", hash = "sha256:6394973f990536e34c05157179146c020284c42fe01da1dfeb0ba16c345280d9", size = 5504576, upload-time = "2026-07-30T15:40:34.512Z" }, ] +[[package]] +name = "waitress" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/cb/04ddb054f45faa306a230769e868c28b8065ea196891f09004ebace5b184/waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f", size = 179901, upload-time = "2024-11-16T20:02:35.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e", size = 56232, upload-time = "2024-11-16T20:02:33.858Z" }, +] + [[package]] name = "whitenoise" version = "6.12.0" @@ -1448,60 +1249,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/b3/af176d79a8515a8a720eccdad9a96f6e31a30abf2865430c8c42adf2fd13/wrapt-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b1e5aa486e269b00ed35e64771c7d0ab8096cfd2643405ca8cd60ebedc099a51", size = 81774, upload-time = "2026-07-28T06:05:53.902Z" }, { url = "https://files.pythonhosted.org/packages/00/39/3daf9f47be208606586de4568ba6713db53ebc8fd7a575aea1fe57983b69/wrapt-2.3.0-py3-none-any.whl", hash = "sha256:d8c7ed08477429752b8c44991f40ad7838b18332a160698740a6bfbc10d998a2", size = 61866, upload-time = "2026-07-28T06:06:12.9Z" }, ] - -[[package]] -name = "zope-event" -version = "6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/93/41/faa10af34d48d9cd6fa0249a1162943ad84a9590bd1a06939981e6640416/zope_event-6.2.tar.gz", hash = "sha256:b97d5d6327067ee6b9dfcbdf606ade9ade70991e19c162e808ea39e5fcf0f8d3", size = 18958, upload-time = "2026-04-28T06:24:10.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/33/848922889e946d4befc415c219fe516af75c49555d8e736e183bfd30db42/zope_event-6.2-py3-none-any.whl", hash = "sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874", size = 6525, upload-time = "2026-04-28T06:24:09.176Z" }, -] - -[[package]] -name = "zope-interface" -version = "8.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/dc/50550cfcbb2ea3cbca5f1d7ed05c8aa840f831a0f2d63aec0a953f7c590e/zope_interface-8.5.tar.gz", hash = "sha256:7a3ba1c5877f0f3e3906b02ddf793abed2becc2948116414ce0e1dd820b68d6d", size = 257957, upload-time = "2026-05-26T06:50:14.574Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/43/9cd98bee951d23848de690ba2809f87e3b22c67c370987acc960da15ad37/zope_interface-8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0c8aa2bf8f3911ef37b87deb1bbe225a310e6eb6522a16d77f5d8330c4f6fbe", size = 210951, upload-time = "2026-05-26T06:49:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/17/0f/8f1a29966bcf863e3a2121edcafb81c55715de7886bcc9544749cc79e7da/zope_interface-8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:efe234a0fafb4b6b1602e9be9245b97c2bf06d67c07af5a4bc3c0438978b555c", size = 211309, upload-time = "2026-05-26T06:49:02.732Z" }, - { url = "https://files.pythonhosted.org/packages/9f/9f/37e564eaaf85e3abc1ada40a79fa43f2ab45bdb67431b0ec0fe29e4763e2/zope_interface-8.5-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:dabeb6fe1228d411994f300811edc6866fff0cdcbc9cef98a78f05ea0da42e37", size = 254881, upload-time = "2026-05-26T06:49:04.303Z" }, - { url = "https://files.pythonhosted.org/packages/06/61/e6501d8ea7a2cac3217e03f404e1f98c1df7191d83cfe86b1895fbba5dac/zope_interface-8.5-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:147a9442dcc2b7339ecdb1be2b3cdb098e90462e39425054053ebfb50d99125a", size = 259811, upload-time = "2026-05-26T06:49:06.373Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/bfa25ef480b02af6e9452c478483fec75e87c9e2b60c407fd0b1f6054b9c/zope_interface-8.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a17e681224267880707c9ec9e730ad9a1ad2d65c371256843efba6cf48711b58", size = 260358, upload-time = "2026-05-26T06:49:08.317Z" }, - { url = "https://files.pythonhosted.org/packages/64/51/2b518072fea76242da64451d501c69b7b5ccdef9b57fead584ccf1c180d5/zope_interface-8.5-cp310-cp310-win_amd64.whl", hash = "sha256:d178968a1a611df30549a717d1624cb38ca810347339e3e37b7baa6f6781a170", size = 214822, upload-time = "2026-05-26T06:49:10.441Z" }, - { url = "https://files.pythonhosted.org/packages/ce/f1/83ad110fb847413affe71609bb50e59e1aa082e1236030122227c7c283d3/zope_interface-8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:afc66ccaef2a3c0bef6ca02aad40d29a39276389dad16a8eac36f9f385e4d057", size = 211426, upload-time = "2026-05-26T06:49:12.595Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a7/6b6e0c31ac240cb9fc015ae9ed45ca54be886c18fcf7bfa2377a4d7a8785/zope_interface-8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28044972187245d7a309e4699319bfdbd2ffcbf7176d1d4ddf5adffb2dea80f", size = 211850, upload-time = "2026-05-26T06:49:14.474Z" }, - { url = "https://files.pythonhosted.org/packages/37/36/7599ecabcf80ce4fef2e1ef3c5ac0d4696b61f03f724cc44022f4d226af9/zope_interface-8.5-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:03bbecc7982af713d7499d4084bc03916413d17ffd45f89009348cc0c1d9e376", size = 260711, upload-time = "2026-05-26T06:49:16.568Z" }, - { url = "https://files.pythonhosted.org/packages/03/3e/1774b0ee46ccbb5498ee3c33ece40315b6ef58bc71957be94bd345340bc1/zope_interface-8.5-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf917009a4a7457c7290225a019f4a0aa706d96accd2cfdba2418d3bc1fcde2f", size = 265277, upload-time = "2026-05-26T06:49:18.656Z" }, - { url = "https://files.pythonhosted.org/packages/b6/09/e533b2ffabaae4e5d5730d6768a591cf335defe8e37bec2ad905d09be656/zope_interface-8.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:31cff25b2aaedb5267e6e77b1e9be6b0ec4f622032de8a069202b8ffacda7dc2", size = 266369, upload-time = "2026-05-26T06:49:20.174Z" }, - { url = "https://files.pythonhosted.org/packages/49/4a/3ebe6a4c122b2d5340db45cbe7e490663d3228b172710ec71060cd5d541e/zope_interface-8.5-cp311-cp311-win_amd64.whl", hash = "sha256:17a3114bbdddb5e75e5784cdf318944636190cbbc72d357ef9fb1a8b0351f955", size = 215161, upload-time = "2026-05-26T06:49:21.799Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/056ad97af5b16db1975ee98ec7ab03d2ce3f3355efad904ced1dbce0e39f/zope_interface-8.5-cp311-cp311-win_arm64.whl", hash = "sha256:aab6bb5bee10f38ea688b95ba054396b67f613552d2c8378be7fcb2d2fba7646", size = 213481, upload-time = "2026-05-26T06:49:25.085Z" }, - { url = "https://files.pythonhosted.org/packages/97/cc/b84123a948f3162a34623e188922827cd845244fdd043ed20f8d02228caa/zope_interface-8.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8e6ee90c2e6de7c37058d5fa41f123c8b13a312db8d1e0fb5840d7f4bcdff9c9", size = 212165, upload-time = "2026-05-26T06:49:26.566Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/cbceec44f1b27208a76c1a688c131302685852406a23df5aab68324109cc/zope_interface-8.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1adc90d3576b3b4c4de4953e6002c37bef28b78d7fa54c1bbfd0c50f022fe7c", size = 212341, upload-time = "2026-05-26T06:49:28.182Z" }, - { url = "https://files.pythonhosted.org/packages/e1/c3/005032195ff3b210c139b7c560ed5c534e844b0907d8e44d2b3d8919305e/zope_interface-8.5-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e6347b8d8d12c5eca6502450a92be30079b7acfade2c4f693efa0deb8871b06e", size = 265296, upload-time = "2026-05-26T06:49:29.741Z" }, - { url = "https://files.pythonhosted.org/packages/c5/66/1036543d6a66bc04c19df3cf650f3ad938a002ab0a443c24e23e8de5e8b9/zope_interface-8.5-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5e970dabea777a24b0b0bbf9dae3ab75ce8b2d8e948edf4875627034b21f3560", size = 270689, upload-time = "2026-05-26T06:49:31.767Z" }, - { url = "https://files.pythonhosted.org/packages/30/4c/8b56259558cace4414e753ca6740396a1f59d4a95ddb55b4658600408670/zope_interface-8.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0b48ccadaa9839e09ff81e969703cecb3f402c813bfe8b958652e699bea69f5", size = 270280, upload-time = "2026-05-26T06:49:33.489Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ea/649908c83aa8fdb7faf2ddca4d3cf6fb8f2157121267dc56e8f72681e26c/zope_interface-8.5-cp312-cp312-win_amd64.whl", hash = "sha256:e0e311f1277468c08fd59a2b41f71b43d25dff639789d364747acd1705c0df6e", size = 215019, upload-time = "2026-05-26T06:49:35.607Z" }, - { url = "https://files.pythonhosted.org/packages/9f/97/da13037b4c563e4df32eedbc819f8c00b754af494f68211e3dffd48d52da/zope_interface-8.5-cp312-cp312-win_arm64.whl", hash = "sha256:652b73107a04159ec6c020db6c1543d4f1e8f4d069bd2aac88a947820923517b", size = 213569, upload-time = "2026-05-26T06:49:37.317Z" }, - { url = "https://files.pythonhosted.org/packages/f4/8c/4c15755d701f2ec0e80d64a18e1ebaf5be2c584c0ec153fd516f5d13eada/zope_interface-8.5-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:28e80457c134d1fa57a7d758004dece348654e1b1467ac22dcdc20fc1d127c52", size = 212512, upload-time = "2026-05-26T06:49:38.996Z" }, - { url = "https://files.pythonhosted.org/packages/9a/2e/4360c54c465db042cc8fbeeec92abac28b4cedbf6ba63c1f092fd08a190f/zope_interface-8.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:09495ce9d559c06b70f2d4855b3e4f48a822a9ddc8be1d30c5b4e5be14ae1ace", size = 212541, upload-time = "2026-05-26T06:49:41.186Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a5/692a2b8d70f78e848793231d5fae5fecbf8d0cccd73430fdc34802a6d3c1/zope_interface-8.5-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:7849ad8fa90763cc1087f4dda78ca3a233e950b3e08fac7079297c9cafbbd7bb", size = 265191, upload-time = "2026-05-26T06:49:43.449Z" }, - { url = "https://files.pythonhosted.org/packages/70/8d/454a9cfc7a050c394ab4f11b3371f7897828b7415e096afff724637e65e0/zope_interface-8.5-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5578c9421ca409a1f39f153d6f7803e4cde01da592ec75a9ac5e1b777d18d33b", size = 270626, upload-time = "2026-05-26T06:49:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/db8409cfa3575b8e9b4800babd7d49f8228433cd1f0c56814bd0ada49c33/zope_interface-8.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e1bd7d96b4ca5fa311f54c9eac16dce4886b428c1531dbe06067763ccdf123b4", size = 270444, upload-time = "2026-05-26T06:49:47.025Z" }, - { url = "https://files.pythonhosted.org/packages/4a/df/a386940e41469ef615e100a216d8b386521e9e598817147f87932ca203c4/zope_interface-8.5-cp313-cp313-win_amd64.whl", hash = "sha256:0c8123d2a4dfde2a613c7cb772605477724782c20bc2e0ad1d9435376a6a44a3", size = 215021, upload-time = "2026-05-26T06:49:48.478Z" }, - { url = "https://files.pythonhosted.org/packages/89/75/477eb5669b6b2a7a843decd1a075e9b1971a8720017654143a7183abd3d9/zope_interface-8.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d02be14f3173c6c7288bc2fdf530090c01c3cf8764ad46c68024686f364278e", size = 213610, upload-time = "2026-05-26T06:49:50.01Z" }, - { url = "https://files.pythonhosted.org/packages/d4/19/5032e954827fdf02db2d2f49737ac4378bb9cfc2cd95a8f2e2a5ae2ec01a/zope_interface-8.5-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:ffaecf013251a89d0de6feb49a46eba48ad8cbbf8a40aeb6045e459e7bec6784", size = 212597, upload-time = "2026-05-26T06:49:51.63Z" }, - { url = "https://files.pythonhosted.org/packages/f1/53/3ef644012cf8a6a234a2d6134aab5a5c65ac5467c86296865501d4fbc406/zope_interface-8.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:126fa9d1c52295ae076d4cf968634f0a1826afa408a20808b57ff72877b8f69f", size = 212626, upload-time = "2026-05-26T06:49:53.236Z" }, - { url = "https://files.pythonhosted.org/packages/32/67/bc8b4f465d388039255003e230c284a175cedf1203c692f23cb7bff64efe/zope_interface-8.5-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:3090e3a663d20194756a59a272e0c8508b889341e31d5894223331fe6b4f9b21", size = 266827, upload-time = "2026-05-26T06:49:54.873Z" }, - { url = "https://files.pythonhosted.org/packages/a7/eb/37d05b935ede53d79690fecc8d201440084418e590bcfc05f384451c7593/zope_interface-8.5-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9342fb74e2afefdb081bf1df727d209ea56995c6e13f5a0540e6d7aff4beafb8", size = 270139, upload-time = "2026-05-26T06:49:57.116Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0b/fd0c54579e2ce8dc6cf1a757903f3374bc6fbda929a46af9e0f53cb0e5f0/zope_interface-8.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c54725d818f1b57a7efb8b16528326e1f3c257b602b32393fd255c45af8799d", size = 270338, upload-time = "2026-05-26T06:49:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/c1/1d/c420dcd777bb761067ea92879ac766694a5ca78608185f1aecea64cbfc11/zope_interface-8.5-cp314-cp314-win_amd64.whl", hash = "sha256:29d74febbae1afeb6834c4ccbf42e242a673c860060f09e53142825270456140", size = 215789, upload-time = "2026-05-26T06:50:00.405Z" }, - { url = "https://files.pythonhosted.org/packages/62/94/50b5eb8f94e527edceac14f9955e58917424ea79bb572ddc18548561cbc2/zope_interface-8.5-cp314-cp314-win_arm64.whl", hash = "sha256:633c8c49396f38df030340797c533e9fe460d1b5d1e42d88e55e938e525f548c", size = 213757, upload-time = "2026-05-26T06:50:01.973Z" }, - { url = "https://files.pythonhosted.org/packages/17/6f/5d5f32c4dfcdb16ce2ec5363da686840f13c13e1a1214cb70b49e1cd6d9f/zope_interface-8.5-cp314-cp314t-macosx_10_9_x86_64.whl", hash = "sha256:133999820fdbae513c36c03d6f29ef87317aaa3edef39112222b155083664714", size = 213591, upload-time = "2026-05-26T06:50:03.529Z" }, - { url = "https://files.pythonhosted.org/packages/f3/55/de0c3459ff717fce3342f9a29464c281fdeb0d36c3171ee88d119d5f0650/zope_interface-8.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8bd75c96966e573232f0599deaff717564828031c7f05563ccc1ac35c5ee0304", size = 213733, upload-time = "2026-05-26T06:50:05.101Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/d97430abd5ae9677e8b9295b58720c0064a5b557dbb6b8bf5928484cf0d8/zope_interface-8.5-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:14b0e9799351d4c34fe99afd67f0cdd76e55ba15c66a98699d5fc22ea8241e08", size = 294905, upload-time = "2026-05-26T06:50:07.384Z" }, - { url = "https://files.pythonhosted.org/packages/41/ec/a0f8f3dad6e74992f4654bdd94802be0929eabca7b871cac3b6fbb5e961b/zope_interface-8.5-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cd6a732ac84b94eb1ef9222a117347a27efd294ee16810ffdf7ecd307677ed5", size = 300885, upload-time = "2026-05-26T06:50:08.997Z" }, - { url = "https://files.pythonhosted.org/packages/0f/da/6881b48803a0ee8d23eb5efa30fce3ed218a2bd9de5758ce489d224fee81/zope_interface-8.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:798b7c87d0e59a7d5d086d642208d0d8700ff0d55c4029134b3c479c3bfb110f", size = 304672, upload-time = "2026-05-26T06:50:10.563Z" }, - { url = "https://files.pythonhosted.org/packages/2e/0e/b4c01320859ff1d585438bc231fd60bd258d096359bccf6654fecdf0cffb/zope_interface-8.5-cp314-cp314t-win_amd64.whl", hash = "sha256:0fc3a9d45f114d27eaa1e53beeb144533689edca8a9f66505b1e8e8b3f075e42", size = 217241, upload-time = "2026-05-26T06:50:12.171Z" }, -] From 72ca4f20693dc78f1d938509e2ec994a85b1801e Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 07:17:40 +0000 Subject: [PATCH 11/30] py3.14: drop python-gflags, modernize protobuf, fix removed stdlib - target Python 3.14 (requires-python, .python-version, pre-commit); enable ruff UP (pyupgrade) at py314 - protobuf 3.20 -> 6.x; FieldDescriptor.label -> is_repeated/is_required - remove python-gflags; reduce pykeg.proto.kbapi to its exception re-exports (the gflags-based client now lives only in the kegbot-api project) - fix 3.14 removals: pkgutil.get_loader -> importlib.util.find_spec, datetime.utcnow() -> now(UTC); fakeredis FakeConnection -> FakeRedisConnection --- .pre-commit-config.yaml | 3 +- .python-version | 1 + bin/kegbot | 8 +- pykeg/core/util.py | 21 ++- pykeg/logging/logger.py | 5 +- pykeg/proto/kbapi.py | 224 +--------------------------- pykeg/proto/protoutil.py | 8 +- pykeg/settings.py | 2 +- pyproject.toml | 11 +- uv.lock | 311 ++------------------------------------- 10 files changed, 41 insertions(+), 553 deletions(-) create mode 100644 .python-version diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d45da3b..1f0fda22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,5 @@ default_language_version: - # Bumped to python3.14 in the Python 3.14 phase. - python: python3.11 + python: python3.14 repos: - repo: https://github.com/tsvikas/sync-with-uv diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..6324d401 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/bin/kegbot b/bin/kegbot index d8f036b2..6c325630 100755 --- a/bin/kegbot +++ b/bin/kegbot @@ -9,12 +9,6 @@ from django.core import management from pykeg.core.util import get_version from pykeg.util import bugreport -if sys.version_info < (2, 7): - sys.stderr.write( - "Error: Kegbot needs Python 2.7 or newer.\n\nCurrent version: %s\n" % sys.version - ) - sys.exit(1) - if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pykeg.settings") @@ -23,7 +17,7 @@ if __name__ == "__main__": cmd = sys.argv[1] if cmd == "version": # Hack: Django's `version` command cannot be overridden the usual way. - print("kegbot-server {}".format(get_version())) + print(f"kegbot-server {get_version()}") sys.exit(0) elif cmd == "bugreport": ret = bugreport.take_bugreport() diff --git a/pykeg/core/util.py b/pykeg/core/util.py index d57bcd0a..194da389 100644 --- a/pykeg/core/util.py +++ b/pykeg/core/util.py @@ -3,11 +3,10 @@ # Note: imports should be limited to python stdlib, since methods here # may be used in models.py, settings.py, etc. +import importlib.util import logging import os -import pkgutil import tempfile -from builtins import object, str from collections import OrderedDict from contextlib import closing from importlib import metadata as importlib_metadata @@ -47,7 +46,7 @@ def should_upgrade(installed_verison, new_version): def get_user_agent(): - return "KegbotServer/%s" % get_version() + return f"KegbotServer/{get_version()}" def CtoF(t): @@ -58,10 +57,10 @@ def get_plugin_template_dirs(plugin_list): ret = [] for plugin in plugin_list: plugin_module = ".".join(plugin.split(".")[:-1]) - pkg = pkgutil.get_loader(plugin_module) - if not pkg: - raise ImproperlyConfigured('Cannot find plugin "%s"' % plugin) - template_dir = os.path.join(os.path.dirname(pkg.path), "templates") + spec = importlib.util.find_spec(plugin_module) + if not spec or not spec.origin: + raise ImproperlyConfigured(f'Cannot find plugin "{plugin}"') + template_dir = os.path.join(os.path.dirname(spec.origin), "templates") if os.path.isdir(template_dir): ret.append(template_dir) return ret @@ -89,14 +88,14 @@ def download_to_tempfile(url): r = requests.get(url, stream=True) ext = os.path.splitext(url)[1] fd, pathname = tempfile.mkstemp(suffix=ext) - logger.info("Downloading file %s to path %s" % (url, pathname)) + logger.info(f"Downloading file {url} to path {pathname}") with closing(os.fdopen(fd, "wb")): for chunk in r.iter_content(chunk_size=1024): if chunk: os.write(fd, chunk) return str(pathname) except requests.exceptions.RequestException as e: - raise IOError("Could not download file: {}".format(e)) + raise OSError(f"Could not download file: {e}") def get_runtime_version_info(): @@ -111,7 +110,7 @@ def get_runtime_version_info(): return OrderedDict(sorted(ret.items())) -class SuppressTaskErrors(object): +class SuppressTaskErrors: """Suppresses certain errors that occur while scheduling tasks.""" def __init__(self, logger=None): @@ -123,6 +122,6 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): exc_info = (exc_type, exc_val, exc_tb) if isinstance(exc_val, RedisError): - self.logger.error("Error scheduling task: {}".format(exc_val), exc_info=exc_info) + self.logger.error(f"Error scheduling task: {exc_val}", exc_info=exc_info) return True return False diff --git a/pykeg/logging/logger.py b/pykeg/logging/logger.py index 784f837e..76d83ca3 100644 --- a/pykeg/logging/logger.py +++ b/pykeg/logging/logger.py @@ -32,7 +32,6 @@ import inspect import logging import socket -from builtins import str from pykeg.core.util import get_current_request @@ -109,7 +108,7 @@ def __init__(self, name, lvl, fn, lno, msg, args, exc_info, func=None, extra=Non "line_no": self.lineno, "msg": str(msg), "args": list(args), - "time": datetime.datetime.utcnow(), + "time": datetime.datetime.now(datetime.UTC), "username": self.username, "funcname": self.funcname, "hostname": self.hostname, @@ -137,6 +136,6 @@ def makeRecord( if extra: for key in extra: if (key in ["message", "asctime"]) or (key in record.__dict__): - raise KeyError("Attempt to overwrite %r in RedisLogRecord" % key) + raise KeyError(f"Attempt to overwrite {key!r} in RedisLogRecord") record.__dict__[key] = extra[key] return record diff --git a/pykeg/proto/kbapi.py b/pykeg/proto/kbapi.py index 4183667a..fbff9816 100644 --- a/pykeg/proto/kbapi.py +++ b/pykeg/proto/kbapi.py @@ -1,10 +1,10 @@ -"""Kegweb API client.""" +"""Kegweb API exceptions. -import datetime -from builtins import object - -import gflags -import requests +These are re-exported here so server code can `from pykeg.proto import kbapi` +and reference e.g. `kbapi.NoAuthTokenError`. The old gflags-based HTTP client +lived here too; it was unused by the server and now lives in the kegbot-api +project. +""" from .exceptions import ( BadApiKeyError, @@ -17,10 +17,7 @@ RequestError, ServerError, ) -from .util import AttrDict -# Exceptions are re-exported here so callers can `from pykeg.proto import kbapi` -# and reference e.g. `kbapi.NoAuthTokenError`. __all__ = [ "BadApiKeyError", "BadRequestError", @@ -32,212 +29,3 @@ "RequestError", "ServerError", ] - -gflags.DEFINE_float( - "api_timeout", - 10.0, - "Socket timeout, in seconds, for Kegbot web API operations. " - "Note that this timeout only applies to blocking socket operations " - "(such as opening a connection) and not I/O.", -) - -FLAGS = gflags.FLAGS - -_DEFAULT_URL = "http://localhost:8000/api/" -_DEFAULT_KEY = "" -try: - from pykeg import settings - - if hasattr(settings, "KEGWEB_BASE_URL"): - _DEFAULT_URL = "%s/api/" % getattr(settings, "KEGWEB_BASE_URL") - if hasattr(settings, "KEGWEB_API_KEY"): - _DEFAULT_KEY = settings.KEGWEB_API_KEY -except ImportError: - # Non-fatal if we can't load settings. - pass - -gflags.DEFINE_string("api_url", _DEFAULT_URL, "Base URL for the Kegweb HTTP api.") - -gflags.DEFINE_string("api_key", _DEFAULT_KEY, "Access key for the Kegweb HTTP api.") - -### begin common - - -def decode_response(response): - """Decodes the requests response object as a JSON response. - - For normal responses, the return value is the Python JSON-decoded 'result' - field of the response. If the response is an error, a RemoteError exception - is raised. - """ - - status_code = response.status_code - response_dict = response.json() - - if "error" in response_dict: - # Response had an error: translate to exception. - err = response_dict["error"] - code = err.get("code", status_code) - message = err.get("message", None) - e = ErrorCodeToException(code, message) - raise e - elif "object" in response_dict or "objects" in response_dict: - # Response was OK, return the result. - return AttrDict(response_dict) - else: - # WTF? - raise ValueError("Invalid response from server: missing result or error") - - -### end common - - -class Client(object): - """Kegweb RESTful API client.""" - - def __init__(self, api_url=None, api_key=None): - if api_url is None: - api_url = FLAGS.api_url - if api_key is None: - api_key = FLAGS.api_key - self._api_url = api_url - self._api_key = api_key - - def _get_url(self, endpoint): - base = self._api_url.rstrip("/") - endpoint = endpoint.strip("/") - return "%s/%s" % (base, endpoint) - - def _http_get(self, endpoint, params=None): - """Issues a GET request to the endpoint, and retuns the result. - - Keyword arguments are passed to the endpoint as GET arguments. - - For normal responses, the return value is the Python JSON-decoded 'object' - or 'objects' field of the response. If the response is an error, a - RemoteError exception is raised. - - If there was an error contacting the server, or in parsing its response, a - ServerError is raised. - """ - return self._http_request(endpoint, params=params) - - def _http_post(self, endpoint, post_data, params=None): - """Issues a POST request to the endpoint, and returns the result. - - For normal responses, the return value is the Python JSON-decoded 'object' - or 'objects' field of the response. If the response is an error, a - RemoteError exception is raised. - - If there was an error contacting the server, or in parsing its response, a - ServerError is raised. - """ - return self._http_request(endpoint, params=params, post_data=post_data) - - def _http_request(self, endpoint, params=None, post_data=None): - """Issues a POST or GET request, depending on the arguments.""" - headers = { - "X-Kegbot-Api-Key": self._api_key, - } - url = self._get_url(endpoint) - - try: - if post_data: - r = requests.post( - url, params=params, data=post_data, headers=headers, timeout=FLAGS.api_timeout - ) - else: - r = requests.get(url, params=params, headers=headers, timeout=FLAGS.api_timeout) - except requests.exceptions.RequestException as e: - raise RequestError(e) - - return decode_response(r) - - def record_drink( - self, - tap_name, - ticks, - volume_ml=None, - username=None, - pour_time=None, - duration=0, - auth_token=None, - spilled=False, - shout="", - ): - endpoint = "/taps/%s" % tap_name - post_data = { - "tap_name": tap_name, - "ticks": ticks, - } - if volume_ml is not None: - post_data["volume_ml"] = volume_ml - if username is not None: - post_data["username"] = username - if duration > 0: - post_data["duration"] = duration - if auth_token is not None: - post_data["auth_token"] = auth_token - if spilled: - post_data["spilled"] = spilled - if shout: - post_data["shout"] = shout - if pour_time: - post_data["pour_time"] = int(pour_time.strftime("%s")) - post_data["now"] = int(datetime.datetime.now().strftime("%s")) - return self._http_post(endpoint, post_data=post_data).object - - def cancel_drink(self, seqn, spilled=False): - endpoint = "/cancel-drink" - post_data = { - "id": seqn, - "spilled": spilled, - } - return self._http_post(endpoint, post_data=post_data).object - - def log_sensor_reading(self, sensor_name, temperature, when=None): - endpoint = "/thermo-sensors/%s" % (sensor_name,) - post_data = { - "temp_c": float(temperature), - } - # TODO(mikey): include post data - return self._http_post(endpoint, post_data=post_data).object - - def status(self): - """Gets complete system status.""" - return self._http_get("status").object - - def taps(self): - """Gets the status of all taps.""" - return self._http_get("taps").objects - - def get_token(self, auth_device, token_value): - url = "auth-tokens/%s/%s" % (auth_device, token_value) - try: - return self._http_get(url).object - except ServerError as e: - raise NotFoundError(e) - - def drinks(self): - """Gets a list of all drinks.""" - return self._http_get("drinks").objects - - def sound_events(self): - """Gets a list of all sound events.""" - return self._http_get("sound-events").objects - - def create_controller(self, controller_name, model_name="unknown", serial_number="unknown"): - post_data = { - "name": controller_name, - "model_name": model_name, - "serial_number": serial_number, - } - return self._http_post("controllers", post_data=post_data).object - - def create_flow_meter(self, controller_id, port_name, ticks_per_ml=2.2): - post_data = { - "port_name": port_name, - "controller": controller_id, - "ticks_per_ml": ticks_per_ml, - } - return self._http_post("flow-meters", post_data=post_data).object diff --git a/pykeg/proto/protoutil.py b/pykeg/proto/protoutil.py index f2268dd5..ac1e8e13 100644 --- a/pykeg/proto/protoutil.py +++ b/pykeg/proto/protoutil.py @@ -8,7 +8,7 @@ def ProtoMessageToDict(message): # if not message.IsInitialized(): # raise ValueError, 'Message not initialized' for descriptor, value in message.ListFields(): - if descriptor.label == descriptor.LABEL_REPEATED: + if descriptor.is_repeated: if descriptor.type == descriptor.TYPE_MESSAGE: ret[descriptor.name] = [ProtoMessageToDict(v) for v in value] else: @@ -24,20 +24,20 @@ def ProtoMessageToDict(message): def DictToProtoMessage(values, out_message): for name, field in out_message.DESCRIPTOR.fields_by_name.items(): if name not in values: - if field.label == field.LABEL_REQUIRED: + if field.is_required: raise ValueError(f"Missing required field {name}") continue value = values.get(name) if field.type == field.TYPE_MESSAGE: inner_message = getattr(out_message, name) - if field.label == field.LABEL_REPEATED: + if field.is_repeated: for subval in value: DictToProtoMessage(subval, inner_message.add()) else: DictToProtoMessage(value, inner_message) else: - if field.label == field.LABEL_REPEATED: + if field.is_repeated: out = getattr(out_message, name) for v in value: if isinstance(v, datetime.datetime): diff --git a/pykeg/settings.py b/pykeg/settings.py index 3ab32454..d9fb5f68 100644 --- a/pykeg/settings.py +++ b/pykeg/settings.py @@ -185,7 +185,7 @@ if _cache.get("BACKEND") == "django_redis.cache.RedisCache": _cache["LOCATION"] = "redis://localhost:6379/0" _cache["OPTIONS"]["CONNECTION_POOL_KWARGS"] = { - "connection_class": fakeredis.FakeConnection + "connection_class": fakeredis.FakeRedisConnection } INTERNAL_IPS = ("127.0.0.1",) diff --git a/pyproject.toml b/pyproject.toml index 03270026..a2d70ae4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "1.3.0" description = "Kegbot Server" authors = [{ name = "The Kegbot Project contributors" }] license = "MIT" -requires-python = ">=3.10.4,<4" +requires-python = ">=3.14,<4" dependencies = [ "addict", "coloredlogs", @@ -28,9 +28,8 @@ dependencies = [ "oauthlib", "Pillow", "pilkit", - "protobuf==3.20.0", + "protobuf>=5,<7", "psycopg2", - "python-gflags", "PyYAML", "redis", "requests", @@ -62,7 +61,7 @@ package = true [tool.ruff] line-length = 100 -target-version = "py310" +target-version = "py314" exclude = [ ".git", ".venv", @@ -77,9 +76,7 @@ exclude = [ quote-style = "preserve" [tool.ruff.lint] -# UP (pyupgrade) is added in the Python 3.14 phase; keep this to format + -# pycodestyle/pyflakes/isort for the tooling swap. -select = ["E", "F", "I"] +select = ["E", "F", "I", "UP"] ignore = ["E501"] [build-system] diff --git a/uv.lock b/uv.lock index cef72e55..6d9a554b 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.10.4, <4" +requires-python = ">=3.14, <4" [[package]] name = "addict" @@ -15,23 +15,11 @@ wheels = [ name = "asgiref" version = "3.12.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, ] -[[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 = "certifi" version = "2026.7.22" @@ -56,58 +44,6 @@ version = "3.4.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, - { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, - { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, - { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, - { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, - { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, - { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, - { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, - { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, - { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, - { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, - { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, - { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, - { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, - { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, - { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, - { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, - { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, - { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, - { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, - { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, - { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, - { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, - { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, - { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, - { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, - { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, - { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, - { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, - { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, - { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, - { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, @@ -351,18 +287,6 @@ 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 = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - [[package]] name = "fakeredis" version = "2.37.0" @@ -370,7 +294,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "redis" }, { name = "sortedcontainers" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/34/73256495885f68b125b6a95f6a8448e103adf34825746ab8905db52183a0/fakeredis-2.37.0.tar.gz", hash = "sha256:7461f124dcba04a80691d72270b3d1d5cd100ef14dc068c76db825940f3ed799", size = 236339, upload-time = "2026-07-22T20:04:53.753Z" } wheels = [ @@ -493,7 +416,6 @@ dependencies = [ { name = "pillow" }, { name = "protobuf" }, { name = "psycopg2" }, - { name = "python-gflags" }, { name = "pyyaml" }, { name = "redis" }, { name = "requests" }, @@ -537,9 +459,8 @@ requires-dist = [ { name = "oauthlib" }, { name = "pilkit" }, { name = "pillow" }, - { name = "protobuf", specifier = "==3.20.0" }, + { name = "protobuf", specifier = ">=5,<7" }, { name = "psycopg2" }, - { name = "python-gflags" }, { name = "pyyaml" }, { name = "redis" }, { name = "requests" }, @@ -565,10 +486,6 @@ version = "2.2.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/eb/b0/9df076488cb2e536d40ce6dbd4273c1f20a386e31ffe6e7cb613902b3c2a/mysqlclient-2.2.8.tar.gz", hash = "sha256:8ed20c5615a915da451bb308c7d0306648a4fd9a2809ba95c992690006306199", size = 92287, upload-time = "2026-02-10T10:58:37.405Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/d0/22b896a2f8f797ee3517a0c1c1fe576cd38768cd25e2a7362a7277725ed4/mysqlclient-2.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:86db31bba7b3480fec2751350e9790e24f016f89af33a87bab7e79f7196474e8", size = 206873, upload-time = "2026-02-10T10:58:40.996Z" }, - { url = "https://files.pythonhosted.org/packages/44/f6/6253d116c024aeb11fb193ad14e2bae2bd3380690e885028abf798b1b0b4/mysqlclient-2.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60c9ed339dc09e3d5380cc2a9f42e86754fee25a661ced77a02df77990664c92", size = 206875, upload-time = "2026-02-10T10:58:42.223Z" }, - { url = "https://files.pythonhosted.org/packages/db/1f/7d6b3fbfc8a317805fdb8bbfbc23af99f2090497090e82513daf491a29ce/mysqlclient-2.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:000c7ec3d11e7c411db832e4cfcd7f05db47464326381f5d5ae991b4bb572f93", size = 207171, upload-time = "2026-02-10T10:58:39.827Z" }, - { url = "https://files.pythonhosted.org/packages/e6/d5/76e369b0fdccd2eb9ed7d890e4e3e23aa1344fea62f0180d7f1574285e54/mysqlclient-2.2.8-cp313-cp313-win_amd64.whl", hash = "sha256:a81f5e12f8d05439709cb02fba97f9f76d1a6c528164f2260d8798fec969e300", size = 207158, upload-time = "2026-02-10T10:58:38.663Z" }, { url = "https://files.pythonhosted.org/packages/42/44/6ae21b4e8490eb5167d04fa7452c462ffd72a502b96bf531477ef044a380/mysqlclient-2.2.8-cp314-cp314-win_amd64.whl", hash = "sha256:9bed7c8d3b629bdc09e17fb628d5b3b0a5fd1f12b09432b464b9126c727bedc0", size = 211644, upload-time = "2026-02-10T10:58:45.022Z" }, { url = "https://files.pythonhosted.org/packages/66/e5/037d55623be9f681236e04abe12e1290847c06bd48270c3f19ac33493cbf/mysqlclient-2.2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:260cce0e81446c83bf0a389e0fae38d68547d9f8fc0833bc733014e10ce28a99", size = 213067, upload-time = "2026-02-10T10:58:43.389Z" }, ] @@ -618,45 +535,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/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, - { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, - { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, - { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, - { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, - { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, - { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, - { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, - { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, - { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, - { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, - { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, - { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, - { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, - { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, - { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, - { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, - { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, @@ -699,11 +577,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, - { 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]] @@ -742,16 +615,17 @@ wheels = [ [[package]] name = "protobuf" -version = "3.20.0" +version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/3e/40c56d21154a1f3ababb69f675333d7fb70c8293f9ca42ea3e448327fc50/protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702", size = 216509, upload-time = "2022-04-01T18:26:17.992Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/33/ef7e1a21a4428e6e57820db4312a674c0dd1228be5ad57c0a776c425f9d1/protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4", size = 962269, upload-time = "2022-04-01T18:21:33.164Z" }, - { url = "https://files.pythonhosted.org/packages/75/b6/a49a33779d1309236aded36234bac5560d0f605f75114e4398b30a786735/protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e", size = 917927, upload-time = "2022-04-01T18:21:35.731Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a6/9a451d69492a695e2300643933590762aa4e5bbef2ae5f08f7d218ea6c3a/protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b", size = 1050440, upload-time = "2022-04-01T18:21:38.049Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e3/6ea01d64c981b81e0f65b68ea3d022aa870f3fd4be6845f4d02c1f64cb86/protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499", size = 779973, upload-time = "2022-04-01T18:21:40.421Z" }, - { url = "https://files.pythonhosted.org/packages/10/fe/280a6ffbe14a1ac458b741591de43a9ae212a3fe088812ad03eb9a4b10ae/protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab", size = 903844, upload-time = "2022-04-01T18:21:43.534Z" }, - { url = "https://files.pythonhosted.org/packages/ae/80/9eaa62a2afcc5407a6b7d2652c208f073df3a5c83b5bff90bf99553fbcf2/protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983", size = 162127, upload-time = "2022-04-01T18:26:16.304Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] [[package]] @@ -760,10 +634,6 @@ version = "2.9.12" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c7/bc/f66df707ed1aec949fbf24e4460e4f4277a7ba23cdadb3965bb1f634ddb9/psycopg2-2.9.12.tar.gz", hash = "sha256:1dedb1c7a1d8552c4a6044c6b1c41a52e6a8e2d144af83eccac758076b1b7c15", size = 379683, upload-time = "2026-04-20T23:36:11.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/7e/940ade9113c6cdd8b4c0b27f820006bdf91c30170a5a95e8332104d5d3ed/psycopg2-2.9.12-cp310-cp310-win_amd64.whl", hash = "sha256:d5fbe092315fb007c03544704e6d1e678a6c0378139d01cea433dc59edf041b4", size = 2756868, upload-time = "2026-04-20T23:33:13.049Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/9cc069f8efc92383d3823ef8805c932f5fda723b3f07d2a8ee019ae2919f/psycopg2-2.9.12-cp311-cp311-win_amd64.whl", hash = "sha256:2532c0cdc6ad18c9c35cd935cc3159712e14f05276a6d29a6435c52d24b840c1", size = 2756862, upload-time = "2026-04-20T23:33:17.206Z" }, - { url = "https://files.pythonhosted.org/packages/7e/b2/7319dc488444b1dfe9ce89c4440df1d5425e2579e6ce9a63e2b70f648491/psycopg2-2.9.12-cp312-cp312-win_amd64.whl", hash = "sha256:83d48e66e18c301d832e93c984a7bcbc0f4ac3bb79e2137e3bc335978c756dc0", size = 2757068, upload-time = "2026-04-20T23:33:20.664Z" }, - { url = "https://files.pythonhosted.org/packages/91/ab/03403779a251ca14a7f1b07a41c7aa735fd451f0aebe4c4f901a231167a4/psycopg2-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:3d23e684927d37b95cee9a943f6927b04ae2fdcd056fd0e2a30929ee89fee5a9", size = 2757176, upload-time = "2026-04-20T23:33:23.577Z" }, { url = "https://files.pythonhosted.org/packages/73/4a/a3566f77501c21a6c2a1fc234dbe5dff74a86e3f150ef070e4ffb835e7f9/psycopg2-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:a73d5513bfe929c56555006c7a9cc7ae6e4276aa99dd2b1e2544eb8bb54f8b23", size = 2848588, upload-time = "2026-04-20T23:33:25.983Z" }, ] @@ -800,12 +670,10 @@ version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ @@ -848,56 +716,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/07/a89b539750a159d5101c4eb9fc84e2961f65cefbd5e0b7440b284471c0b0/python_discovery-1.5.1-py3-none-any.whl", hash = "sha256:ac07f44cade589d954e9d6a1e1468539fdddd2cf676beb51da73e0f156b7c932", size = 35752, upload-time = "2026-07-31T22:06:01.116Z" }, ] -[[package]] -name = "python-gflags" -version = "3.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/ec/e31302d355bcb9d207d9b858adc1ecc4a6d8c855730c8ba4ddbdd3f8eb8d/python-gflags-3.1.2.tar.gz", hash = "sha256:40ae131e899ef68e9e14aa53ca063839c34f6a168afe622217b5b875492a1ee2", size = 52093, upload-time = "2017-10-27T13:04:08.546Z" } - [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, @@ -922,9 +746,6 @@ wheels = [ name = "redis" version = "8.1.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/a8/99/604f0b666d4c616d891cf77ebb9db6bb21601344c051aebf1b72b9ff915f/redis-8.1.0.tar.gz", hash = "sha256:6e1a19beef9225c83efd689c7e6b7da2d5215b1f42cd13b7fc3714d0a09c7b25", size = 5254356, upload-time = "2026-07-30T08:51:00.269Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/66/9d/c5731f6e3608663d4d3656fd8d3aecee8b509c3082818f5a13eae925baea/redis-8.1.0-py3-none-any.whl", hash = "sha256:a4fe1aac3d3b3cc791d4b3d5931c5a956045dc951ee74d1c913ee3ac4d2ee9fb", size = 560618, upload-time = "2026-07-30T08:50:58.497Z" }, @@ -1036,60 +857,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, ] -[[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 = "typing-extensions" version = "4.16.0" @@ -1139,7 +906,6 @@ dependencies = [ { name = "filelock" }, { name = "platformdirs" }, { name = "python-discovery" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ea/fa/18004e5cb15541ad2a68ff219c755233b012b12d4ec8663d06a258082bec/virtualenv-21.7.1.tar.gz", hash = "sha256:d0dbfaa5483487baea28d7210ef8d24c9d1bd0f10f449eeb215568825a9b334e", size = 5525237, upload-time = "2026-07-30T15:40:36.36Z" } wheels = [ @@ -1170,61 +936,6 @@ version = "2.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2b/b0/c1f5a970721f06b85c0cd5142e0ff8fe067708abd779b0c4f4be7d61d09f/wrapt-2.3.0.tar.gz", hash = "sha256:681a2d0eefd721998f90642762b8e75c2159ec531b20ad5e437245ea7b06a107", size = 131509, upload-time = "2026-07-28T06:06:14.895Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/31/5822ce37ca8820c2ed35a498c67c8b37960b9cee2ba437fd32849d0a234c/wrapt-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0bb2797048db0956348cb3058c33bc4184614f13231389cfbccc16a5d32780a7", size = 81191, upload-time = "2026-07-28T06:04:04.858Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5a/3c6117938be98754578ab83f5a40d7d0ea2cd2c487dc5cd6027ee7228229/wrapt-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce9f398f868d2b3b27aa2ea4de79645ef9077aeeac8dfc2814b0d542c6a2b87f", size = 82255, upload-time = "2026-07-28T06:04:07.151Z" }, - { url = "https://files.pythonhosted.org/packages/a5/0f/94ae724c5087eb6054c0d63febd7094947dcf302fe058e2e0488102a872b/wrapt-2.3.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad71df7a04dd3497e9302e81f4a7c91bd401ea0e15a9df9029527900f94bee43", size = 155228, upload-time = "2026-07-28T06:04:08.272Z" }, - { url = "https://files.pythonhosted.org/packages/6c/21/1f780bba935dcf697c0c59de9be3a559bbb8e31a53ca3f25422023738432/wrapt-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc82c2ccc8e234c844f5303d9f2984b346dcdd53e94823ce8420d2c75b4b9023", size = 157073, upload-time = "2026-07-28T06:04:09.459Z" }, - { url = "https://files.pythonhosted.org/packages/73/31/6c7799d7b6431fcd7e1b83245fb45258a2d2c3a2187fbaecb83572a72d7a/wrapt-2.3.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6e19531ae33c508cea7d84a7edfda01fa86e51b8d1a93a77712c55e6e469152", size = 151594, upload-time = "2026-07-28T06:04:10.784Z" }, - { url = "https://files.pythonhosted.org/packages/ce/17/42d670dbfafd49076c6eb2b7d67633d7e1c968e39bfb11a135acb6fac67b/wrapt-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df4ce31150bcd5d9f36f816aac3010ab4f4bf8672ac1d3b0ac7d539ec61c7c02", size = 156069, upload-time = "2026-07-28T06:04:12.316Z" }, - { url = "https://files.pythonhosted.org/packages/1f/d6/c66b4ba4eda49257c84d5c2df26118280f09ca7905aee20d0064db778d13/wrapt-2.3.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e2e692bc0d63f881cf7006730a56bd4e0c2fab5dc318466942805d692b166276", size = 150930, upload-time = "2026-07-28T06:04:13.482Z" }, - { url = "https://files.pythonhosted.org/packages/c0/f2/1a3b949c0322fb27396eafd1044328c1cb0400e0b32105d75a3cd03096e7/wrapt-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c8388ba7faf5dbf9ee106bb70d66f257629b1bd98091123e19e8a4553a319199", size = 154525, upload-time = "2026-07-28T06:04:14.698Z" }, - { url = "https://files.pythonhosted.org/packages/12/65/147563a3dfa6e830c857b93b530ebd8c0cd9d540e5914aec8f9b12880c02/wrapt-2.3.0-cp310-cp310-win32.whl", hash = "sha256:e045ff75d7d94900fc32896ed93c45ce2d2cac28c9dead582ff9a5a49d446e35", size = 77879, upload-time = "2026-07-28T06:04:16.102Z" }, - { url = "https://files.pythonhosted.org/packages/c4/eb/921405b4dc55d4f8be4c700ef120539fdd75d5fdb50d83bd257171ee18e0/wrapt-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4fc96b159af0a3e0faa72475a69d66292bea72a5bed1e1aca1bffbddc3cb2b0", size = 80733, upload-time = "2026-07-28T06:04:17.43Z" }, - { url = "https://files.pythonhosted.org/packages/b6/13/75947450c5bb57795fa86384721cd52c5c4deb0879022f309501a8a85d44/wrapt-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:1236fa25173ca964c97422470482e9011b9e3c7ed0d75798b40b3da3b0e0e760", size = 80199, upload-time = "2026-07-28T06:04:18.761Z" }, - { url = "https://files.pythonhosted.org/packages/00/b8/9182e4c618a847be0baccb68e4602b070d0fa22c782cf058f4bc66b32709/wrapt-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ab559e1b2551d23d54db2a0001c6d73bad022a254639561c5f6c382a9d6c2fe", size = 81427, upload-time = "2026-07-28T06:04:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/84/ca/613cefd9c5977366b1587e61c0b428176d382e6d75b454084c5e58503042/wrapt-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bff9a671bc00709cab5a7f745c592b5671873449db0ee2a569af994f16b29a4d", size = 82360, upload-time = "2026-07-28T06:04:21.613Z" }, - { url = "https://files.pythonhosted.org/packages/71/71/4cd2151a236f44a6e2dd4ed8011838d7ba0be3d656c8bafdfc65a2ed1917/wrapt-2.3.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc648a335d7e01adb3640b25f02fd0ea05886cf04d0af7f4ee902bc7b5e466e8", size = 161700, upload-time = "2026-07-28T06:04:22.723Z" }, - { url = "https://files.pythonhosted.org/packages/49/2c/bc508fee75eb2919ed69769800b09968e4aab16897f909a23f39c81e323f/wrapt-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0077f3d65541925fa83002f967b22ad6550d24813ac64cb905f717194128d9c", size = 162922, upload-time = "2026-07-28T06:04:24.177Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e5/04f34d38e66d857dfc2fc4088d60e70c0e422467822defa49b2b4a26e17b/wrapt-2.3.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9790ea25190a4e0fe4cdf4eeb868e9d75f8a024a70a5b6bf9c348a3a2b72e731", size = 156125, upload-time = "2026-07-28T06:04:25.58Z" }, - { url = "https://files.pythonhosted.org/packages/23/41/c35940ea1c423f129ebe4361db853bc80d4def6326242e1206fa15bf94f4/wrapt-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:816877aa749253149f9ecfd2635d4d948ecfa338e1a0311d187b1acb1bb8a3eb", size = 162039, upload-time = "2026-07-28T06:04:27.154Z" }, - { url = "https://files.pythonhosted.org/packages/0e/60/9bda34c3d7d182aa703fe35339ae0ed4c4dad5e5c587f93890143e1f87fb/wrapt-2.3.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d1c2c1b808600d2ea808e6360910a60ed5f409a4011655e10f9164ba0a414a6", size = 155110, upload-time = "2026-07-28T06:04:28.497Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ba/60bfd9b1a751f4fcb2d603668fc272d651ccdd339a56acf8c40ad21a0293/wrapt-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5ba1e5e08ddc46130e9682b2c249f2d1dd39bda9106ed4bd401b7519f18f41bd", size = 161089, upload-time = "2026-07-28T06:04:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/0f/32/2bd358c6f4f1305c813479d1e9ba746bebdd794f4a20107ab2b3ee0cbd45/wrapt-2.3.0-cp311-cp311-win32.whl", hash = "sha256:45c9279b373d15649dfa2c2077cb3408ea1a6d3125afbdab9d6b809a66f68e14", size = 78030, upload-time = "2026-07-28T06:04:31.241Z" }, - { url = "https://files.pythonhosted.org/packages/4a/62/ecc969b13b141fef89b888c9760821cb01a86ac8fc953911592c8e1e1522/wrapt-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:195b1842b4122fb54e3cd3dd5b2b4aa49302a5a61da901df0481f5c97aedde84", size = 80944, upload-time = "2026-07-28T06:04:32.655Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3d/9278ada8a2b3f24372b630361e84e9a7de7abc3784634860c26d1c37785a/wrapt-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:6db604ef0c67bdb2042ecdfd7b7f037cf09733557ca42360d1018285634f7b98", size = 80074, upload-time = "2026-07-28T06:04:33.811Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4a/d17a0fad1bf1c5f2c887ff71fef75654141b0880bff71d157d955b5bec3a/wrapt-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a45ffae742ce91a16e11cb6c7cd71e7f9994f3cbd283b962ab093f5c6dcf525", size = 82139, upload-time = "2026-07-28T06:04:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/6e/55/51b92daaf6defb57f4dc56bdcce985400f75c6984a03ca5e78ccac717028/wrapt-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69e477046f2237ef0bc6547544ee73008dc764ca26eff44f09e976d221b34d5d", size = 82723, upload-time = "2026-07-28T06:04:36.502Z" }, - { url = "https://files.pythonhosted.org/packages/28/7f/cfd9bc4b1f5e424eeea83d0493e43f3b1b02707ce8e50c47945873982bd5/wrapt-2.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d221a6e6ddd302b8397433184e96b59f259f50024b854db1c411a881586b6b8", size = 172381, upload-time = "2026-07-28T06:04:37.674Z" }, - { url = "https://files.pythonhosted.org/packages/cb/89/ff7814f6eb6856b479946117d1138a2fbb46cdb6b1f379db359056c69743/wrapt-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:392158c9a7f2ab1b8699418bfc0fe6f83548788c418b27d7bf2019ad3405cebb", size = 174120, upload-time = "2026-07-28T06:04:38.987Z" }, - { url = "https://files.pythonhosted.org/packages/12/1e/8eded8615d39e3ce81f626937a3a87b280a2a86239a2bf14a4b4bb345034/wrapt-2.3.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e5301c35cf75655eb33498f2bd6ae8703ca19940e3167dc9cdf740c712a39c60", size = 163035, upload-time = "2026-07-28T06:04:40.361Z" }, - { url = "https://files.pythonhosted.org/packages/35/ea/a0af2d9da62897af2a055484920de05dade30d2ba2c0d65cbdea875d3d8b/wrapt-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:418f54bb09d1762db02c7009b4051149893af3153a87f92d70356703c11eea02", size = 171887, upload-time = "2026-07-28T06:04:41.614Z" }, - { url = "https://files.pythonhosted.org/packages/7e/dd/63cd4c864c65ef4906df64bd2d378f4a62b54f28063f282dfb3bf93caead/wrapt-2.3.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1598becd30f8f2777d18564064eb4f4dbe1ab0e05a8f09786d0ef505ac782bf3", size = 161113, upload-time = "2026-07-28T06:04:42.864Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ee/82f1fc9e431b5c2c5a6d201aa865dbeae3984c311c6d11a185f0c8367cf6/wrapt-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3da470536bf9645143323dd41b32db55c6f4304ad382094c1a1da8a92061e10d", size = 170530, upload-time = "2026-07-28T06:04:44.212Z" }, - { url = "https://files.pythonhosted.org/packages/37/a5/5dc590e863a419930d988f8b7ca3e75a6befcfb10b6003b3a152f3d5f732/wrapt-2.3.0-cp312-cp312-win32.whl", hash = "sha256:fb8e2e6704a1e0b1b989546c69e2688371ef4a07fa5f61bde3eb6211186f5ac1", size = 78323, upload-time = "2026-07-28T06:04:45.484Z" }, - { url = "https://files.pythonhosted.org/packages/51/f9/4a6925a07951df56394f7e6ebe14f69f1c5ef9d87aa63e0839acf15aa63a/wrapt-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdc021cb0b62471d6aac7f2bd92f3b4658073775f9ee7fcd325c511129e7bcc8", size = 81180, upload-time = "2026-07-28T06:04:47.021Z" }, - { url = "https://files.pythonhosted.org/packages/a8/4f/8b5de0395b2a72216751d41c9861df6facaeb611b619d8810ed2b3b23eb2/wrapt-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:67bfe2485f50368c3fcd2275fc1fd100e350d601e0058921a7c82678a465aeab", size = 80155, upload-time = "2026-07-28T06:04:48.373Z" }, - { url = "https://files.pythonhosted.org/packages/8e/6e/0f88a072483e76b881e3fdcd6b6ffb4a5791002514fe541e72b1b73c859a/wrapt-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d3fb71e65b001adfc42684522eeccd9c21d8ba679945abc993439567b66e59f", size = 81960, upload-time = "2026-07-28T06:04:49.622Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ff/b7e2776e7c294075eb712cc9ef573d1b818f393006d09787262b8fc871c4/wrapt-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51a7a4181c1295774812271fbcd7c909df372bc25579d4ed9eb875caaf0ae86f", size = 82435, upload-time = "2026-07-28T06:04:50.9Z" }, - { url = "https://files.pythonhosted.org/packages/d8/90/343bb5d0f1f9669bc252a6073f085b4abf862511bd5c9c9eaec754341f1d/wrapt-2.3.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9045917809c63fdf7abe3a2ceaed3d670b8ee4500ddd9291192d30aeb34467c5", size = 170350, upload-time = "2026-07-28T06:04:52.187Z" }, - { url = "https://files.pythonhosted.org/packages/59/f8/13b79a392930bd0dd6b86cbfbfe1c40944110456e1dc6d809e5c46ece904/wrapt-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54ca1d5573f69b5fe1d74f1f65799c68015e82f685efec9fd8cfa40a094c44d0", size = 170022, upload-time = "2026-07-28T06:04:53.599Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fc/4f1b6918f5290db959d6e0c07f77385d87cede29c39c9cf8f145e9c82954/wrapt-2.3.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:242b60c21e30866e6a2fa606c612b47c553fa60c0eaeeeb7797fb842ac0ce609", size = 161043, upload-time = "2026-07-28T06:04:54.936Z" }, - { url = "https://files.pythonhosted.org/packages/01/e1/45d3cf74414780bdff6d0380467e003f6eb0f028b6c9403db868dbc7209c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3f3d7ec0a51fbfe00d3aef047641ff2c58b25565b4717fc1f90e050be01cba8", size = 168576, upload-time = "2026-07-28T06:04:56.261Z" }, - { url = "https://files.pythonhosted.org/packages/f3/73/2fa58dd97f191c997755e2c6d569a68f0c433db4e4b36099bdd7227b6cac/wrapt-2.3.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:261f53870cd4fb2bf38f9f972c56c728fd224cb7c65721307de59d9e7e6741ae", size = 159140, upload-time = "2026-07-28T06:04:57.754Z" }, - { url = "https://files.pythonhosted.org/packages/29/a8/08a56e2000a8816d449dcbad8c8b081697acbbd490821ceca0f9d8e8d20c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8159ec0b0cb7608175eb150de94c19e34f4d47ac655f5ca9baf45df6b688ffd3", size = 169263, upload-time = "2026-07-28T06:04:59.161Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d4/354e1725e35a73b2af4fa70a3e024c7a5d1bf1802dfb862dcb668aae0253/wrapt-2.3.0-cp313-cp313-win32.whl", hash = "sha256:10461884b3014fbfc8eb7d09a93c5f246363e6711d9d881f95eb8c27fdef049f", size = 78241, upload-time = "2026-07-28T06:05:00.507Z" }, - { url = "https://files.pythonhosted.org/packages/6c/7e/34c87fa2174848dfee820322aaa318bab08913998ccecc8d2f57b4ad4639/wrapt-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac870cc97b73bb00ac353329e9559a4bebc47c4c86792ed9b23b58c15b6ad838", size = 81113, upload-time = "2026-07-28T06:05:01.839Z" }, - { url = "https://files.pythonhosted.org/packages/11/86/fcc9a530579e008c9478bb565a6cdfbfd33536660f069c8b91a6607c5050/wrapt-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:a65e8db2b4e90c2e7ade931086351c98ef420bf7a94ee08c95ac8a3cbbc43579", size = 80182, upload-time = "2026-07-28T06:05:03.152Z" }, - { url = "https://files.pythonhosted.org/packages/96/50/3864848b95b28ef73e17551fc8dccbff2628a834f52cf26a57f9c419fb83/wrapt-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fd1f2f557dd3491fe75905e578f4db967393d40d1a8f468edc4d40ac7f2d5944", size = 83921, upload-time = "2026-07-28T06:05:04.476Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4c/3d1921a60c3e8c71c540ff136e6a47a1fbccf7f671e818394889f7871d9c/wrapt-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9f5d2aec29dfc76c37e23897dee92766a3fd4f3bff3ae7fc9c6b4bf37d8c1360", size = 84412, upload-time = "2026-07-28T06:05:05.921Z" }, - { url = "https://files.pythonhosted.org/packages/fa/1a/4a796ff7adb26ada6d4b758c94d47a38320b085e7099afc088efbbcdb006/wrapt-2.3.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:646d20d413ffcd1b0a2f700076e2d0252d872dcb7754860a73e45a59ea883614", size = 207168, upload-time = "2026-07-28T06:05:07.256Z" }, - { url = "https://files.pythonhosted.org/packages/1d/3e/d7777776806c579b761bac2f91721dda9f04c7a1b380213c5935cc750ae6/wrapt-2.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:379f670f45b7bb8993edd9f6fc36c6cc65edb81cffa0b504be34acb0303fff0a", size = 214351, upload-time = "2026-07-28T06:05:08.945Z" }, - { url = "https://files.pythonhosted.org/packages/63/27/2d64d394df7bf181955b3bb562bf33c4492fb4be113f53071106d43ad8b5/wrapt-2.3.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6208f302f110295d64b22a7ac96500c791bf492dce4366e622e4912b077c9687", size = 199020, upload-time = "2026-07-28T06:05:10.418Z" }, - { url = "https://files.pythonhosted.org/packages/3e/3d/fb31d3db7d9834d265fb1a27a2adf0ddf51557c67458c97b22439ad6ae3d/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ed635a9ca4f3a5a2b900c10c69e823373bc00ebc114b459383596d3487da3570", size = 209969, upload-time = "2026-07-28T06:05:11.983Z" }, - { url = "https://files.pythonhosted.org/packages/1f/d1/8724b5da582e62070dc9bf4d8bf1972f317297eefd7ba1f2b5c6393ccf6c/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e3b9eaa742ae7a0aaaaad4ca4b69469d757af2d6e6663ef1dadc47adec0aeb41", size = 196324, upload-time = "2026-07-28T06:05:13.557Z" }, - { url = "https://files.pythonhosted.org/packages/0d/5c/3d9ef411149543016ee6bcf3af707f787cebd946527452b94bf122e9b7b4/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0f7284f88f4833705132d06d3b425a43095c2cbd07c58166aac3ab646ba12a4", size = 202610, upload-time = "2026-07-28T06:05:15.048Z" }, - { url = "https://files.pythonhosted.org/packages/13/9b/4fc042ceb757866dd4a5fc057b3b736f2b360d3703ce9f830d83dc9226e0/wrapt-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:7ebb274aba688b043429eb1500ff8a76ce0cb8ac0812ca3e301f06247b8722b3", size = 79178, upload-time = "2026-07-28T06:05:16.469Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ff/b94878f8eed809ca042685276bcea9f24e8c2ca7c9653bb80bbb920a68a5/wrapt-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c4bded758ad6f03b965830944a2f0bc5b2eb3767fe5a7310134315d1a6610e98", size = 82634, upload-time = "2026-07-28T06:05:18.026Z" }, - { url = "https://files.pythonhosted.org/packages/80/fb/663e1de5332a71685a729754312d327d4cada767c36e1c5a2db4c8de49e6/wrapt-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:d2cc64539da63e39ffb9c7ede849b6e8ddaaf7b3876b5cfb04efd85a5f3f4eb6", size = 81387, upload-time = "2026-07-28T06:05:19.417Z" }, { url = "https://files.pythonhosted.org/packages/58/10/b073beaea89bc0d3670a75ff51139430a54b6af7ba7796507730634536dd/wrapt-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea52a0d0f08c584943d5764be0e84efa912c8da23c23e1e285ff2f5641c18fcc", size = 81978, upload-time = "2026-07-28T06:05:21.133Z" }, { url = "https://files.pythonhosted.org/packages/b3/31/0916d9cebf848ed3f1a0c1888faee421747df77331e4db2bc527a9a85988/wrapt-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd85b0aa88efdb189d6ae2f35f4526943a8f091c38599c9c31478241c819e6a1", size = 82518, upload-time = "2026-07-28T06:05:22.562Z" }, { url = "https://files.pythonhosted.org/packages/f5/73/31c1bf0f3384062751c2094dadb314916d70aa9b6bfd26d994b4a7b393fa/wrapt-2.3.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:141ed6211286a9660d8d6702de598b43f0934b4f0eda16393f100a80f501d945", size = 170187, upload-time = "2026-07-28T06:05:23.904Z" }, From a2e5056fe8ac938e50d3f503b34fba79c8fb0c35 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 07:17:47 +0000 Subject: [PATCH 12/30] style: modernize syntax for python 3.14 (ruff pyupgrade sweep) - f-strings, `class X:` over `class X(object)`, drop `from builtins`/`__future__` shims, and other pyupgrade rewrites across the tree --- docs/source/conf.py | 1 - pykeg/api/auth.py | 2 +- pykeg/backup/backup.py | 32 ++-- pykeg/backup/backup_test.py | 2 +- pykeg/backup/mysql.py | 9 +- pykeg/config.py | 2 +- pykeg/contrib/webhook/plugin.py | 2 +- pykeg/contrib/webhook/tasks.py | 6 +- pykeg/core/admin.py | 4 +- pykeg/core/backend_test.py | 12 +- pykeg/core/cache.py | 3 +- pykeg/core/fields.py | 2 +- pykeg/core/management/commands/backup.py | 6 +- pykeg/core/management/commands/common.py | 13 +- pykeg/core/management/commands/erase.py | 3 +- pykeg/core/management/commands/rename_user.py | 4 +- pykeg/core/management/commands/restore.py | 2 +- pykeg/core/management/commands/run_all.py | 4 +- pykeg/core/management/commands/upgrade.py | 21 +-- pykeg/core/models.py | 137 ++++++++---------- pykeg/core/stats.py | 23 ++- pykeg/core/stats_test.py | 2 +- pykeg/core/tasks.py | 4 +- pykeg/core/tests.py | 6 +- pykeg/core/testutils.py | 2 +- pykeg/core/time_series.py | 4 +- pykeg/core/util_test.py | 2 - pykeg/notification/__init__.py | 15 +- pykeg/notification/backends/base.py | 4 +- pykeg/notification/backends/email.py | 8 +- pykeg/notification/backends/email_test.py | 74 ++++------ pykeg/notification/forms.py | 4 +- pykeg/plugin/datastore.py | 12 +- pykeg/plugin/plugin.py | 3 +- pykeg/plugin/util.py | 8 +- pykeg/proto/protolib.py | 4 +- pykeg/proto/util.py | 4 +- pykeg/test/plugin.py | 4 +- pykeg/util/bugreport.py | 10 +- pykeg/util/dbstatus.py | 2 +- pykeg/util/email_test.py | 1 - pykeg/util/runner.py | 24 ++- pykeg/util/units.py | 7 +- pykeg/web/account/views.py | 4 +- pykeg/web/api/api_test.py | 12 +- pykeg/web/api/devicelink.py | 3 +- pykeg/web/api/forms.py | 4 +- pykeg/web/api/middleware.py | 5 +- pykeg/web/api/util.py | 8 +- pykeg/web/api/validate_jsonp.py | 3 - pykeg/web/api/views.py | 5 +- pykeg/web/auth/__init__.py | 6 +- pykeg/web/auth/local.py | 1 - pykeg/web/charts/charts.py | 4 +- pykeg/web/context_processors.py | 4 +- pykeg/web/kbregistration/forms.py | 5 +- pykeg/web/kegadmin/forms.py | 48 +++--- pykeg/web/kegadmin/views.py | 34 ++--- pykeg/web/kegweb/kbstorage.py | 4 +- pykeg/web/kegweb/kegweb_test.py | 10 +- pykeg/web/kegweb/templatetags/kegweblib.py | 33 ++--- pykeg/web/kegweb/views.py | 4 +- pykeg/web/middleware.py | 4 +- pykeg/web/setup_wizard/forms.py | 4 +- 64 files changed, 311 insertions(+), 388 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d615876f..63c90c50 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # diff --git a/pykeg/api/auth.py b/pykeg/api/auth.py index aff8e4ec..bbbe9cf3 100644 --- a/pykeg/api/auth.py +++ b/pykeg/api/auth.py @@ -4,7 +4,7 @@ from pykeg.core import models -class ApiKeyAuthHolder(object): +class ApiKeyAuthHolder: """DRF auth holder for our api keys.""" def __init__(self, api_key): diff --git a/pykeg/backup/backup.py b/pykeg/backup/backup.py index 5d542b6b..5e43d6a6 100644 --- a/pykeg/backup/backup.py +++ b/pykeg/backup/backup.py @@ -125,7 +125,7 @@ def add_files(storage, dirname, destdir): os.makedirs(output_dirname) with storage.open(full_filename, "r") as srcfile: with open(output_filename, "w") as dstfile: - logger.debug("+++ Creating {}".format(output_filename)) + logger.debug(f"+++ Creating {output_filename}") shutil.copyfileobj(srcfile, dstfile) metadata[META_NUM_MEDIA_FILES] += 1 for subdir in subdirs: @@ -171,7 +171,7 @@ def create_backup_zip(backup_dir, backup_name): rel_path = os.path.relpath(full_path, backup_dir) archive_path = os.path.join(backup_name, rel_path) zf.write(full_path, archive_path) - logger.debug("Added to zip: {}".format(archive_path)) + logger.debug(f"Added to zip: {archive_path}") zf.close() return zipfile_path @@ -186,7 +186,7 @@ def backup(storage=default_storage, include_media=True): date = timezone.now() site_slug = slugify(get_title()) date_str = date.strftime("%Y%m%d-%H%M%S") - backup_name = "{slug}-{date}".format(slug=site_slug, date=date_str) + backup_name = f"{site_slug}-{date_str}" backup_dir = create_backup_tree(date=date, storage=storage, include_media=include_media) try: @@ -198,8 +198,8 @@ def backup(storage=default_storage, include_media=True): for chunk in iter(lambda: f.read(2**20), b""): sha1.update(chunk) digest = sha1.hexdigest() - saved_zip_name = os.path.join(BACKUPS_DIRNAME, "{}-{}.zip".format(backup_name, digest)) - with open(temp_zip, "r") as temp_zip_file: + saved_zip_name = os.path.join(BACKUPS_DIRNAME, f"{backup_name}-{digest}.zip") + with open(temp_zip) as temp_zip_file: ret = storage.save(saved_zip_name, temp_zip_file) return ret finally: @@ -211,7 +211,7 @@ def backup(storage=default_storage, include_media=True): def extract_backup(backup_file): assert sys.version_info[:3] >= (2, 7, 4), "Unsafe to extract ZIPs in this Python version" backup_dir = tempfile.mkdtemp() - logger.debug("Extracting backup to {}".format(backup_dir)) + logger.debug(f"Extracting backup to {backup_dir}") zf = zipfile.ZipFile(backup_file, mode="r") zf.extractall(path=backup_dir) return backup_dir @@ -225,10 +225,10 @@ def verify_backup_directory(backup_dir): if not os.path.exists(metadata_file): raise InvalidBackup("SQL dumpfile does not exist") - metadata = kbjson.loads(open(metadata_file, "r").read()) + metadata = kbjson.loads(open(metadata_file).read()) format = metadata.get(META_BACKUP_FORMAT) if format != BACKUP_FORMAT: - raise InvalidBackup("Unsupported backup format: {}".format(format)) + raise InvalidBackup(f"Unsupported backup format: {format}") return metadata @@ -240,13 +240,13 @@ def restore_media(backup_dir, storage): for filename in files: full_path = os.path.join(dirname, filename) rel_path = os.path.relpath(full_path, media_dir) - with open(full_path, "r") as data: - logger.debug("+++ Restoring file {}".format(rel_path)) + with open(full_path) as data: + logger.debug(f"+++ Restoring file {rel_path}") storage.save(rel_path, data) def restore_from_directory(backup_dir, storage=default_storage): - logger.info("Restoring from {} ...".format(backup_dir)) + logger.info(f"Restoring from {backup_dir} ...") if db_impl.is_installed(): raise AlreadyInstalledError("You must erase this system before restoring.") @@ -255,12 +255,10 @@ def restore_from_directory(backup_dir, storage=default_storage): current_engine = db_impl.engine_name() saved_engine = metadata[META_DB_ENGINE] if current_engine != saved_engine: - raise BackupError( - "Current DB is {}; cannot restore from {}".format(current_engine, db_impl) - ) + raise BackupError(f"Current DB is {current_engine}; cannot restore from {db_impl}") input_filename = os.path.join(backup_dir, SQL_FILENAME) - with open(input_filename, "r") as in_fd: + with open(input_filename) as in_fd: db_impl.restore(in_fd) restore_media(backup_dir, storage) @@ -277,7 +275,7 @@ def restore(backup_file): restore_from_directory(backup_file) return - logger.info("Restoring from {} ...".format(backup_file)) + logger.info(f"Restoring from {backup_file} ...") unzipped_dir = extract_backup(backup_file) try: dirs = os.listdir(unzipped_dir) @@ -298,7 +296,7 @@ def delete_files(dirname): subdirs, files = storage.listdir(dirname) for filename in files: full_name = os.path.join(dirname, filename) - logger.debug("Deleting file: {}".format(full_name)) + logger.debug(f"Deleting file: {full_name}") storage.delete(full_name) for subdir in subdirs: delete_files(dirname) diff --git a/pykeg/backup/backup_test.py b/pykeg/backup/backup_test.py index 39052ce0..7dcedab3 100644 --- a/pykeg/backup/backup_test.py +++ b/pykeg/backup/backup_test.py @@ -124,6 +124,6 @@ def recursive_diff(self, dir1, dir2): f1 = open(f1_full).read() f2 = open(f2_full).read() if f1 != f2: - message = 'Files not equal: "{}" and "{}" differ.'.format(f1_full, f2_full) + message = f'Files not equal: "{f1_full}" and "{f2_full}" differ.' message += "\n" + "".join(difflib.ndiff(f1.splitlines(True), f2.splitlines(True))) self.fail(message) diff --git a/pykeg/backup/mysql.py b/pykeg/backup/mysql.py index 42eb13b2..2bb36a9f 100644 --- a/pykeg/backup/mysql.py +++ b/pykeg/backup/mysql.py @@ -2,7 +2,6 @@ import logging import subprocess -from builtins import str from django.apps import apps from django.conf import settings @@ -40,9 +39,9 @@ def is_installed(): args += ["-e", "'show tables like \"core_kegbotsite\";'"] cmd = " ".join(args) - logger.info("command: {}".format(cmd)) + logger.info(f"command: {cmd}") output = subprocess.check_output(cmd, shell=True) - logger.info("result: {}".format(output)) + logger.info(f"result: {output}") return "core_kegbotsite" in output @@ -87,10 +86,10 @@ def erase(): # Build the sql command. tables = [str(model._meta.db_table) for model in apps.get_models()] - query = ["DROP TABLE IF EXISTS {};".format(t) for t in tables] + query = [f"DROP TABLE IF EXISTS {t};" for t in tables] query = ["SET FOREIGN_KEY_CHECKS=0;"] + query + ["SET FOREIGN_KEY_CHECKS=1;"] query = " ".join(query) - cmd = " ".join(args + ["-e", "'{}'".format(query)]) + cmd = " ".join(args + ["-e", f"'{query}'"]) logger.info(cmd) subprocess.check_call(cmd, shell=True) diff --git a/pykeg/config.py b/pykeg/config.py index 7c721962..c18c0c54 100644 --- a/pykeg/config.py +++ b/pykeg/config.py @@ -53,7 +53,7 @@ def define_setting(name, typefn=str, *, default, required): required: true if the setting _must_ be present in env """ if name in ALL_SETTINGS: - raise ValueError("Bug: Setting {} registered more than once.".format(name)) + raise ValueError(f"Bug: Setting {name} registered more than once.") ALL_SETTINGS[name] = (default, required, typefn) diff --git a/pykeg/contrib/webhook/plugin.py b/pykeg/contrib/webhook/plugin.py index 0d47b468..2a58b171 100644 --- a/pykeg/contrib/webhook/plugin.py +++ b/pykeg/contrib/webhook/plugin.py @@ -24,7 +24,7 @@ def handle_new_events(self, events): self.handle_event(event) def handle_event(self, event): - self.logger.info("Handling new event: %s" % event.id) + self.logger.info(f"Handling new event: {event.id}") settings = self.get_site_settings() urls = settings.get("webhook_urls", "").strip().split() event_dict = protolib.ToDict(event, full=True) diff --git a/pykeg/contrib/webhook/tasks.py b/pykeg/contrib/webhook/tasks.py index f580d2f4..c00b4e78 100644 --- a/pykeg/contrib/webhook/tasks.py +++ b/pykeg/contrib/webhook/tasks.py @@ -20,7 +20,7 @@ def webhook_post(url, event_dict): Event payloads are in the same format as the /api/events/ endpoint. """ - logger.info("Posting webhook: url=%s event=%s" % (url, event_dict)) + logger.info(f"Posting webhook: url={url} event={event_dict}") hook_dict = { "type": "event", @@ -29,11 +29,11 @@ def webhook_post(url, event_dict): headers = { "content-type": "application/json", - "user-agent": "Kegbot/%s" % get_version(), + "user-agent": f"Kegbot/{get_version()}", } try: return requests.post(url, data=kbjson.dumps(hook_dict), headers=headers) except requests.exceptions.RequestException as e: - logger.warning("Error posting hook: %s" % e) + logger.warning(f"Error posting hook: {e}") return False diff --git a/pykeg/core/admin.py b/pykeg/core/admin.py index e0c78c2a..68c16ef9 100644 --- a/pykeg/core/admin.py +++ b/pykeg/core/admin.py @@ -81,11 +81,11 @@ class ThermoSensorAdmin(admin.ModelAdmin): def thermolog_deg_c(obj): - return "%.2f C" % (obj.temp,) + return f"{obj.temp:.2f} C" def thermolog_deg_f(obj): - return "%.2f F" % (CtoF(obj.temp),) + return f"{CtoF(obj.temp):.2f} F" class ThermologAdmin(admin.ModelAdmin): diff --git a/pykeg/core/backend_test.py b/pykeg/core/backend_test.py index 809cc49a..22df7b9f 100644 --- a/pykeg/core/backend_test.py +++ b/pykeg/core/backend_test.py @@ -1,7 +1,5 @@ """Unittests for backends module.""" -from builtins import range - from django.test import TransactionTestCase from pykeg.core import defaults, models @@ -283,17 +281,17 @@ def test_urls(self): producer_name=FAKE_BREWER_NAME, style_name=FAKE_BEER_STYLE, ) - self.assertEqual("http://test.example.com/kegs/{}/".format(keg.id), keg.full_url()) + self.assertEqual(f"http://test.example.com/kegs/{keg.id}/", keg.full_url()) drink = models.Drink.record_drink(METER_NAME, ticks=1, volume_ml=100, photo="foo") - self.assertEqual("http://test.example.com/d/{}/".format(drink.id), drink.short_url()) + self.assertEqual(f"http://test.example.com/d/{drink.id}/", drink.short_url()) self.assertEqual( - "http://test.example.com/s/{}/".format(drink.session.id), drink.session.short_url() + f"http://test.example.com/s/{drink.session.id}/", drink.session.short_url() ) start = drink.session.start_time - datepart = "{}/{}/{}".format(start.year, start.month, start.day) + datepart = f"{start.year}/{start.month}/{start.day}" self.assertEqual( - "http://test.example.com/sessions/{}/{}/".format(datepart, drink.session.id), + f"http://test.example.com/sessions/{datepart}/{drink.session.id}/", drink.session.full_url(), ) diff --git a/pykeg/core/cache.py b/pykeg/core/cache.py index 31ae0884..f031aa78 100644 --- a/pykeg/core/cache.py +++ b/pykeg/core/cache.py @@ -1,5 +1,4 @@ import time -from builtins import object, str from django.conf import settings from django.core.cache import cache as django_cache @@ -10,7 +9,7 @@ SEP = ":" -class KegbotCache(object): +class KegbotCache: """Wrapper around django cache, supporting Kegbot-specific features. Primarily, this wrapper facilitates "generational" or "namespaced" caching, diff --git a/pykeg/core/fields.py b/pykeg/core/fields.py index 3af57824..4b4483bd 100644 --- a/pykeg/core/fields.py +++ b/pykeg/core/fields.py @@ -250,7 +250,7 @@ def __init__(self, *args, **kwargs): kwargs.setdefault("max_length", 3) kwargs.setdefault("choices", COUNTRIES) - super(CountryField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_internal_type(self): return "CharField" diff --git a/pykeg/core/management/commands/backup.py b/pykeg/core/management/commands/backup.py index 14c4ebb5..66df86a9 100644 --- a/pykeg/core/management/commands/backup.py +++ b/pykeg/core/management/commands/backup.py @@ -27,8 +27,8 @@ def handle(self, **options): if hasattr(storage, "location"): path = os.path.join(storage.location, path) print("Backup complete!") - print("Path: {}".format(path)) + print(f"Path: {path}") try: - print("URL: {}".format(storage.url(location))) - except (NotImplementedError, UnknownBaseUrlException): + print(f"URL: {storage.url(location)}") + except NotImplementedError, UnknownBaseUrlException: pass diff --git a/pykeg/core/management/commands/common.py b/pykeg/core/management/commands/common.py index b66ec6fa..f3c212d1 100644 --- a/pykeg/core/management/commands/common.py +++ b/pykeg/core/management/commands/common.py @@ -1,7 +1,6 @@ import os import signal import sys -from builtins import object from django.core.management.base import BaseCommand @@ -25,21 +24,21 @@ def progbar(title, pos, total, width=40): chars = int((float(pos) / total) * width) rem = width - chars inner = "+" * chars + " " * rem - sys.stdout.write("%-30s [%s] %i/%i\r" % (title, inner, pos, total)) + sys.stdout.write(f"{title:<30} [{inner}] {pos}/{total}\r") sys.stdout.flush() def check_and_create_pid_file(pid_file): if os.path.exists(pid_file): - print("Error: already running ({})".format(pid_file)) + print(f"Error: already running ({pid_file})") sys.exit(1) f = open(pid_file, "w") - f.write("{}\n".format(os.getpid())) + f.write(f"{os.getpid()}\n") f.close() -class check_pidfile(object): +class check_pidfile: """Context manager that creates a pidfile, or fails if it exists.""" def __init__(self, pid_file): @@ -47,9 +46,9 @@ def __init__(self, pid_file): def __enter__(self): if os.path.exists(self.pid_file): - raise RuntimeError("Error: already running ({})".format(self.pid_file)) + raise RuntimeError(f"Error: already running ({self.pid_file})") f = open(self.pid_file, "w") - f.write("{}\n".format(os.getpid())) + f.write(f"{os.getpid()}\n") f.close() def __exit__(self, exc_type, exc_val, exc_tb): diff --git a/pykeg/core/management/commands/erase.py b/pykeg/core/management/commands/erase.py index 9c0d7b85..8ba8d93c 100644 --- a/pykeg/core/management/commands/erase.py +++ b/pykeg/core/management/commands/erase.py @@ -1,5 +1,4 @@ import sys -from builtins import input from django.conf import settings from django.core.management.base import BaseCommand @@ -18,7 +17,7 @@ def handle(self, *args, **options): print(" CANNOT BE UNDONE.") print("") print(" Database: {}".format(settings.DATABASES["default"]["NAME"])) - print(" Media: {}".format(settings.MEDIA_ROOT)) + print(f" Media: {settings.MEDIA_ROOT}") print(" ************************************************************************") print("") print("Are you SURE you want to continue? ") diff --git a/pykeg/core/management/commands/rename_user.py b/pykeg/core/management/commands/rename_user.py index e234b8ae..1f7721a0 100644 --- a/pykeg/core/management/commands/rename_user.py +++ b/pykeg/core/management/commands/rename_user.py @@ -22,9 +22,9 @@ def handle(self, *args, **options): try: user = models.User.objects.get(username=from_username) except models.User.DoesNotExist: - raise CommandError('User named "{}" does not exist'.format(from_username)) + raise CommandError(f'User named "{from_username}" does not exist') user.username = to_username user.save() - print('"{}" has been renamed "{}"'.format(from_username, to_username)) + print(f'"{from_username}" has been renamed "{to_username}"') diff --git a/pykeg/core/management/commands/restore.py b/pykeg/core/management/commands/restore.py index 8b81263a..6c8412f8 100644 --- a/pykeg/core/management/commands/restore.py +++ b/pykeg/core/management/commands/restore.py @@ -15,7 +15,7 @@ def handle(self, *args, **options): raise CommandError("Must provide a zipfile path") backup_path = os.path.normpath(os.path.expanduser(args[0])) if not os.path.exists(backup_path): - raise CommandError("Archive does not exist: {}".format(backup_path)) + raise CommandError(f"Archive does not exist: {backup_path}") try: backup.restore(backup_path) diff --git a/pykeg/core/management/commands/run_all.py b/pykeg/core/management/commands/run_all.py index b500e6b9..db64522b 100644 --- a/pykeg/core/management/commands/run_all.py +++ b/pykeg/core/management/commands/run_all.py @@ -11,12 +11,12 @@ def get_commands(self, options): server_command = "kegbot run_server" if logs_dir: - server_command += " --logs_dir={}".format(logs_dir) + server_command += f" --logs_dir={logs_dir}" ret.append(("server", server_command)) workers_command = "kegbot run_workers" if logs_dir: - workers_command += " --logs_dir={}".format(logs_dir) + workers_command += f" --logs_dir={logs_dir}" ret.append(("workers", workers_command)) return ret diff --git a/pykeg/core/management/commands/upgrade.py b/pykeg/core/management/commands/upgrade.py index b6bf483c..10b66bab 100644 --- a/pykeg/core/management/commands/upgrade.py +++ b/pykeg/core/management/commands/upgrade.py @@ -1,5 +1,4 @@ import sys -from builtins import str from django.contrib.staticfiles.management.commands import collectstatic from django.core.management.base import BaseCommand @@ -19,8 +18,8 @@ def run(cmd, args=[]): cmdname = cmd.__module__.split(".")[-1] - arg_str = " ".join("%s" % a for a in args) - print("--- Running command: %s %s" % (cmdname, arg_str)) + arg_str = " ".join(f"{a}" for a in args) + print(f"--- Running command: {cmdname} {arg_str}") cmd.run_from_argv([sys.argv[0], cmdname] + args) @@ -60,32 +59,26 @@ def handle(self, *args, **options): sys.exit(1) if installed_version == app_version and not force: - print("Version {} already installed.".format(installed_version)) + print(f"Version {installed_version} already installed.") return if installed_version > app_version: - print( - "Installed version {} is newer than app version {}".format( - installed_version, app_version - ) - ) + print(f"Installed version {installed_version} is newer than app version {app_version}") sys.exit(1) if installed_version < MINIMUM_INSTALLED_VERSION: print("") print("ERROR: This version of Kegbot can only upgrade systems running on version") print( - "v{} or newer. Please install Kegbot v{} and run `kegbot upgrade` again.".format( - MINIMUM_INSTALLED_VERSION, MINIMUM_INSTALLED_VERSION - ) + f"v{MINIMUM_INSTALLED_VERSION} or newer. Please install Kegbot v{MINIMUM_INSTALLED_VERSION} and run `kegbot upgrade` again." ) - print("(Existing version: {})".format(installed_version)) + print(f"(Existing version: {installed_version})") print("") print("More help: https://github.com/Kegbot/kegbot-server/wiki/Upgrading-Old-Versions") print("") sys.exit(1) - print("Upgrading from {} to {}".format(installed_version, app_version)) + print(f"Upgrading from {installed_version} to {app_version}") self.do_version_upgrades(installed_version) run(migrate.Command(), args=["--noinput", "-v", "0"]) diff --git a/pykeg/core/models.py b/pykeg/core/models.py index 7c0791c7..3bc49a53 100644 --- a/pykeg/core/models.py +++ b/pykeg/core/models.py @@ -4,7 +4,6 @@ import random import re import urllib.parse -from builtins import object, str from uuid import uuid4 from addict import Dict @@ -131,7 +130,7 @@ class User(AbstractBaseUser): USERNAME_FIELD = "username" REQUIRED_FIELDS = ["email"] - class Meta(object): + class Meta: verbose_name = _("user") verbose_name_plural = _("users") @@ -308,7 +307,7 @@ class KegbotSite(models.Model): default=kb_common.DRINK_SESSION_TIME_MINUTES, help_text="Maximum time, in minutes, that a session may be idle (no pours) " "before it is considered to be finished. " - "Recommended value is %s." % kb_common.DRINK_SESSION_TIME_MINUTES, + f"Recommended value is {kb_common.DRINK_SESSION_TIME_MINUTES}.", ) privacy = models.CharField( max_length=63, @@ -390,10 +389,10 @@ def reverse_full(self, *args, **kwargs): def format_volume(self, volume_ml): if self.volume_display_units == "metric": if volume_ml < 500: - return "%d mL" % int(volume_ml) - return "%.1f L" % (volume_ml / 1000.0) + return f"{int(volume_ml)} mL" + return f"{volume_ml / 1000.0:.1f} L" else: - return "%1.f oz" % units.Quantity(volume_ml).InOunces() + return f"{units.Quantity(volume_ml).InOunces():1.0f} oz" def can_invite(self, user): if not user: @@ -455,7 +454,7 @@ def regenerate(self): @classmethod def generate_key(cls): """Returns a new random key.""" - return "%032x" % random.randint(0, 2**128 - 1) + return f"{random.randint(0, 2**128 - 1):032x}" def _sitesettings_post_save(sender, instance, **kwargs): @@ -495,7 +494,7 @@ class BeverageProducer(models.Model): max_length=255, blank=True, null=True, help_text="Future use." ) - class Meta(object): + class Meta: ordering = ("name",) def __str__(self): @@ -605,17 +604,17 @@ class Beverage(models.Model): max_length=255, blank=True, null=True, help_text="Future use." ) - class Meta(object): + class Meta: ordering = ("name",) def __str__(self): - return "{} by {}".format(self.name, self.producer) + return f"{self.name} by {self.producer}" class KegTap(models.Model): """A physical tap of beer.""" - class Meta(object): + class Meta: ordering = ("sort_order", "id") name = models.CharField( @@ -642,7 +641,7 @@ class Meta(object): ) def __str__(self): - return "{}: {}".format(self.name, self.current_keg) + return f"{self.name}: {self.current_keg}" def is_active(self): """Returns True if the tap has an active Keg.""" @@ -825,11 +824,11 @@ class Controller(models.Model): ) def __str__(self): - return "Controller: {}".format(self.name) + return f"Controller: {self.name}" class FlowMeter(models.Model): - class Meta(object): + class Meta: unique_together = ("controller", "port_name") controller = models.ForeignKey( @@ -851,12 +850,12 @@ class Meta(object): ) ticks_per_ml = models.FloatField( default=kb_common.DEFAULT_TICKS_PER_ML, - help_text="Flow meter pulses per mL of fluid. Common values: %s " - "(FT330-RJ), 5.4 (SF800)" % kb_common.DEFAULT_TICKS_PER_ML, + help_text=f"Flow meter pulses per mL of fluid. Common values: {kb_common.DEFAULT_TICKS_PER_ML} " + "(FT330-RJ), 5.4 (SF800)", ) def meter_name(self): - return "{}.{}".format(self.controller.name, self.port_name) + return f"{self.controller.name}.{self.port_name}" def __str__(self): return self.meter_name() @@ -881,20 +880,20 @@ def get_or_create_from_meter_name(cls, meter_name): def get_from_meter_name(cls, meter_name): idx = meter_name.find(".") if idx <= 0: - raise cls.DoesNotExist("Illegal meter_name: %s" % repr(meter_name)) + raise cls.DoesNotExist(f"Illegal meter_name: {repr(meter_name)}") controller_name = meter_name[:idx] port_name = meter_name[idx + 1 :] try: controller = Controller.objects.get(name=controller_name) except Controller.DoesNotExist: - raise cls.DoesNotExist("No such controller: %s" % repr(controller_name)) + raise cls.DoesNotExist(f"No such controller: {repr(controller_name)}") return cls.objects.get(controller=controller, port_name=port_name) class FlowToggle(models.Model): - class Meta(object): + class Meta: unique_together = ("controller", "port_name") controller = models.ForeignKey( @@ -916,10 +915,10 @@ class Meta(object): ) def toggle_name(self): - return "{}.{}".format(self.controller.name, self.port_name) + return f"{self.controller.name}.{self.port_name}" def __str__(self): - return "{} (tap: {})".format(self.toggle_name(), self.tap) + return f"{self.toggle_name()} (tap: {self.tap})" @classmethod def get_or_create_from_toggle_name(cls, toggle_name): @@ -941,14 +940,14 @@ def get_or_create_from_toggle_name(cls, toggle_name): def get_from_toggle_name(cls, toggle_name): idx = toggle_name.find(".") if idx <= 0: - raise cls.DoesNotExist("Illegal toggle_name: %s" % repr(toggle_name)) + raise cls.DoesNotExist(f"Illegal toggle_name: {repr(toggle_name)}") controller_name = toggle_name[:idx] port_name = toggle_name[idx + 1 :] try: controller = Controller.objects.get(name=controller_name) except Controller.DoesNotExist: - raise cls.DoesNotExist("No such controller: %s" % repr(controller_name)) + raise cls.DoesNotExist(f"No such controller: {repr(controller_name)}") return cls.objects.get(controller=controller, port_name=port_name) @@ -1063,7 +1062,7 @@ def get_illustration(self, thumbnail=False): else: level = 0 kind = "thumb" if thumbnail else "full" - img_path = "images/keg/{}/keg-srm14-{}.png".format(kind, level) + img_path = f"images/keg/{kind}/keg-srm14-{level}.png" url = urllib.parse.urljoin(settings.STATIC_URL, img_path) if not urllib.parse.urlparse(url).scheme: @@ -1220,7 +1219,7 @@ def create_keg( )[0] if keg_type not in keg_sizes.DESCRIPTIONS: - raise ValueError("Unrecognized keg type: %s" % keg_type) + raise ValueError(f"Unrecognized keg type: {keg_type}") if full_volume_ml is None: full_volume_ml = keg_sizes.VOLUMES_ML[keg_type] else: @@ -1273,7 +1272,7 @@ def end_keg(self, when=None): return self def __str__(self): - return "Keg #{} - {}".format(self.id, self.type) + return f"Keg #{self.id} - {self.type}" def _keg_pre_save(sender, instance, **kwargs): @@ -1308,7 +1307,7 @@ def _keg_pre_save(sender, instance, **kwargs): class Drink(models.Model): """Table of drinks records""" - class Meta(object): + class Meta: get_latest_by = "time" ordering = ("-time",) @@ -1515,7 +1514,7 @@ def record_drink( # it again. If malformed, just junk it; it's non-essential information. tick_time_series = time_series.to_string(time_series.from_string(tick_time_series)) except ValueError as e: - logger.warning("Time series invalid, ignoring. Error was: %s" % e) + logger.warning(f"Time series invalid, ignoring. Error was: {e}") tick_time_series = "" d = Drink( @@ -1583,13 +1582,13 @@ def cancel_drink(self, spilled=False): signals.drink_canceled.send_robust(sender=self.__class__, drink_id=drink_id) def __str__(self): - return "Drink {} by {}".format(self.id, self.user) + return f"Drink {self.id} by {self.user}" class AuthenticationToken(models.Model): """A secret token to authenticate a user, optionally pin-protected.""" - class Meta(object): + class Meta: unique_together = ("auth_device", "token_value") auth_device = models.CharField(max_length=64, help_text="Namespace for this token.") @@ -1633,9 +1632,9 @@ def __str__(self): elif auth_device == "core.onewire": auth_device = "OneWire" - ret = "{} {}".format(auth_device, self.token_value) + ret = f"{auth_device} {self.token_value}" if self.nice_name: - ret += " ({})".format(self.nice_name) + ret += f" ({self.nice_name})" return ret def get_auth_device(self): @@ -1694,7 +1693,7 @@ def _auth_token_pre_save(sender, instance, **kwargs): class DrinkingSession(models.Model): """A collection of contiguous drinks.""" - class Meta(object): + class Meta: get_latest_by = "start_time" ordering = ("-start_time",) @@ -1707,7 +1706,7 @@ class Meta(object): name = models.CharField(max_length=256, blank=True, null=True) def __str__(self): - return "Session #{}: {}".format(self.id, self.start_time) + return f"Session #{self.id}: {self.start_time}" def Duration(self): return self.end_time - self.start_time @@ -1776,18 +1775,18 @@ def summarize_drinkers(self): ret = "{}, {} and {}".format(*names) else: if guest_trailer: - return "%s, %s and at least %i others" % (names[0], names[1], num - 2) + return f"{names[0]}, {names[1]} and at least {num - 2} others" else: - return "%s, %s and %i others" % (names[0], names[1], num - 2) + return f"{names[0]}, {names[1]} and {num - 2} others" - return "%s%s" % (ret, guest_trailer) + return f"{ret}{guest_trailer}" def GetTitle(self): if self.name: return self.name else: if self.id: - return "Session %s" % (self.id,) + return f"Session {self.id}" else: # Not yet saved. return "New Session" @@ -1859,7 +1858,7 @@ class ThermoSensor(models.Model): def __str__(self): if self.nice_name: - return "{} ({})".format(self.nice_name, self.raw_name) + return f"{self.nice_name} ({self.raw_name})" return self.raw_name def LastLog(self): @@ -1925,7 +1924,7 @@ def log_sensor_reading(self, temperature, when=None): class Thermolog(models.Model): """A log from an ITemperatureSensor device of periodic measurements.""" - class Meta(object): + class Meta: get_latest_by = "time" ordering = ("-time",) @@ -1934,7 +1933,7 @@ class Meta(object): time = models.DateTimeField() def __str__(self): - return "%.2f C / %.2f F [%s]" % (self.TempC(), self.TempF(), self.time) + return f"{self.TempC():.2f} C / {self.TempF():.2f} F [{self.time}]" def TempC(self): return self.temp @@ -1972,7 +1971,7 @@ class Stats(models.Model): DrinkingSession, related_name="stats", null=True, on_delete=models.CASCADE ) - class Meta(object): + class Meta: get_latest_by = "id" unique_together = ("drink", "user", "keg", "session") @@ -1983,7 +1982,7 @@ def apply_usernames(cls, stats): def safe_get_user(pk): try: return User.objects.get(pk=pk) - except (User.DoesNotExist, ValueError): + except User.DoesNotExist, ValueError: return None orig = stats.get("registered_drinkers", []) @@ -2015,7 +2014,7 @@ def get_latest_for_view(cls, user=None, keg=None, session=None): class SystemEvent(models.Model): - class Meta(object): + class Meta: ordering = ("-id",) get_latest_by = "time" @@ -2074,22 +2073,22 @@ class Meta(object): def __str__(self): if self.kind == self.DRINK_POURED: - ret = "Drink {} poured".format(self.drink.id) + ret = f"Drink {self.drink.id} poured" elif self.kind == self.SESSION_STARTED: - ret = "Session {} started by drink {}".format(self.session.id, self.drink.id) + ret = f"Session {self.session.id} started by drink {self.drink.id}" elif self.kind == self.SESSION_JOINED: - ret = "Session {} joined by {} (drink {})".format( - self.session.id, self.user.username, self.drink.id + ret = ( + f"Session {self.session.id} joined by {self.user.username} (drink {self.drink.id})" ) elif self.kind == self.KEG_TAPPED: - ret = "Keg {} tapped".format(self.keg.id) + ret = f"Keg {self.keg.id} tapped" elif self.kind == self.KEG_VOLUME_LOW: - ret = "Keg {} volume low".format(self.keg.id) + ret = f"Keg {self.keg.id} volume low" elif self.kind == self.KEG_ENDED: - ret = "Keg {} ended".format(self.keg.id) + ret = f"Keg {self.keg.id} ended" else: - ret = "Unknown event type ({})".format(self.kind) - return "Event {}: {}".format(self.id, ret) + ret = f"Unknown event type ({self.kind})" + return f"Event {self.id}: {ret}" @classmethod def build_events_for_keg(cls, keg): @@ -2173,7 +2172,7 @@ def _pics_file_name(instance, filename, now=None, uuid_str=None): uuid_str = str(uuid4()).replace("-", "") date_str = now.strftime("%Y%m%d%H%M%S") ext = os.path.splitext(filename)[1] - new_filename = "%s-%s%s" % (date_str, uuid_str, ext) + new_filename = f"{date_str}-{uuid_str}{ext}" return os.path.join("pics", new_filename) @@ -2233,16 +2232,16 @@ class Picture(models.Model): ) def __str__(self): - return "Picture: {}".format(self.image) + return f"Picture: {self.image}" def get_caption(self): if self.caption: return self.caption elif self.drink: if self.user: - return "{} pouring drink {}".format(self.user.username, self.drink.id) + return f"{self.user.username} pouring drink {self.drink.id}" else: - return "An unknown drinker pouring drink {}".format(self.drink.id) + return f"An unknown drinker pouring drink {self.drink.id}" return "" def erase_and_delete(self): @@ -2252,27 +2251,19 @@ def erase_and_delete(self): exists = bool(image_file) if not exists: logger.debug( - "erase_and_delete: image.id={} spec={}: does not exist".format( - self.id, spec - ) + f"erase_and_delete: image.id={self.id} spec={spec}: does not exist" ) continue - logger.debug( - "erase_and_delete: image.id={} spec={}: deleting ...".format(self.id, spec) - ) + logger.debug(f"erase_and_delete: image.id={self.id} spec={spec}: deleting ...") try: try: default_storage.delete(image_file.path) except NotImplementedError: default_storage.delete(image_file.name) - logger.debug( - "erase_and_delete: image.id={} spec={}: deleted".format(self.id, spec) - ) - except IOError as e: - logger.warning( - "erase_and_delete: image.id={} spec={}: error: {}".format(self.id, spec, e) - ) + logger.debug(f"erase_and_delete: image.id={self.id} spec={spec}: deleted") + except OSError as e: + logger.warning(f"erase_and_delete: image.id={self.id} spec={spec}: error: {e}") finally: self.delete() @@ -2280,7 +2271,7 @@ def erase_and_delete(self): class NotificationSettings(models.Model): """Stores a user's notification settings for a notification backend.""" - class Meta(object): + class Meta: unique_together = ("user", "backend") user = models.ForeignKey(User, on_delete=models.CASCADE, help_text="User for these settings.") @@ -2300,7 +2291,7 @@ class Meta(object): class PluginData(models.Model): """Key/value JSON data store for plugins.""" - class Meta(object): + class Meta: unique_together = ("plugin_name", "key") plugin_name = models.CharField(max_length=127, help_text="Plugin short name") diff --git a/pykeg/core/stats.py b/pykeg/core/stats.py index 08357e16..e8cd5332 100644 --- a/pykeg/core/stats.py +++ b/pykeg/core/stats.py @@ -4,7 +4,6 @@ import inspect import logging import zoneinfo -from builtins import object, str from addict import Dict from django.utils.timezone import localtime @@ -16,7 +15,7 @@ logger = logging.getLogger(__name__) -class StatsView(object): +class StatsView: def __init__(self, user=None, session=None, keg=None): self.user = user self.session = session @@ -27,11 +26,11 @@ def __str__(self): if not self.user and not self.keg and not self.session: ret += "system" if self.user: - ret += "user={} ".format(self.user.username) + ret += f"user={self.user.username} " if self.session: - ret += "session={} ".format(self.session.id) + ret += f"session={self.session.id} " if self.keg: - ret += "keg={}".format(self.keg.id) + ret += f"keg={self.keg.id}" return ret def as_tuple(self): @@ -57,7 +56,7 @@ def get_prior_drinks(self, drink): return qs.order_by("-id") -class StatsBuilder(object): +class StatsBuilder: """Derives statistics from drinks.""" def __init__(self): @@ -70,7 +69,7 @@ def build(self, drink, previous_stats): if previous_stats is None: previous_stats = Dict() - logger.debug("build: drink={}".format(drink.id)) + logger.debug(f"build: drink={drink.id}") stats = Dict() for statname, fn in self.functions: @@ -81,7 +80,7 @@ def build(self, drink, previous_stats): val = fn(drink, previous_stats) if val is None: - raise ValueError("Stat generator for %s returned None" % statname) + raise ValueError(f"Stat generator for {statname} returned None") stats[statname] = val return stats @@ -180,7 +179,7 @@ def largest_session(self, drink, previous_stats, previous_value={}): def invalidate(drink_id): """Clears all statistics starting from (and including) drink_id.""" - logger.debug("--- Invalidating stats since id {}".format(drink_id)) + logger.debug(f"--- Invalidating stats since id {drink_id}") models.Stats.objects.filter(drink_id__gte=drink_id).delete() @@ -196,7 +195,7 @@ def build_for_id(drink_id): try: drink = models.Drink.objects.get(pk=drink_id) except models.Drink.DoesNotExist: - logger.warning("No drink exists with id={}".format(drink_id)) + logger.warning(f"No drink exists with id={drink_id}") return _build_all_views(drink) @@ -224,7 +223,7 @@ def _build_single_view(drink, view, prior_stats=None): indicates that the prior stats are unknown. When prior stats are unknown, they will be queried or generated as needed. """ - logger.debug(">>> Building stats for {}".format(view)) + logger.debug(f">>> Building stats for {view}") build_list = [drink] if prior_stats is None: @@ -249,7 +248,7 @@ def _build_single_view(drink, view, prior_stats=None): # Build all drinks on the hit list. for build_drink in build_list: - logger.debug(" - operating on drink {}".format(build_drink.id)) + logger.debug(f" - operating on drink {build_drink.id}") stats = BUILDER.build(drink=build_drink, previous_stats=prior_stats) models.Stats.objects.create( drink=build_drink, diff --git a/pykeg/core/stats_test.py b/pykeg/core/stats_test.py index 9e74964e..9f558128 100644 --- a/pykeg/core/stats_test.py +++ b/pykeg/core/stats_test.py @@ -15,7 +15,7 @@ def setUp(self): test_usernames = ("user1", "user2", "user3") self.users = [ - models.User.create_new_user(name, "%s@example.com" % name) for name in test_usernames + models.User.create_new_user(name, f"{name}@example.com") for name in test_usernames ] self.taps = [ diff --git a/pykeg/core/tasks.py b/pykeg/core/tasks.py index 8d71c4fd..fbc48588 100644 --- a/pykeg/core/tasks.py +++ b/pykeg/core/tasks.py @@ -20,13 +20,13 @@ def schedule_tasks(events): try: plugin.handle_new_events(events) except Exception: - logger.exception("Error dispatching events to plugin {}".format(plugin.get_name())) + logger.exception(f"Error dispatching events to plugin {plugin.get_name()}") notification.handle_new_system_events(events) @job("stats") def build_stats(drink_id, rebuild_following): - logger.info("build_stats drink_id={} rebuild_following={}".format(drink_id, rebuild_following)) + logger.info(f"build_stats drink_id={drink_id} rebuild_following={rebuild_following}") with transaction.atomic(): if rebuild_following: stats.rebuild_from_id(drink_id) diff --git a/pykeg/core/tests.py b/pykeg/core/tests.py index 01f42fa0..9db15774 100644 --- a/pykeg/core/tests.py +++ b/pykeg/core/tests.py @@ -20,10 +20,10 @@ class CoreTests(TestCase): def test_flake8(self): root_path = path_for_import("pykeg") config_file = os.path.join(root_path, "setup.cfg") - command = "flake8 --config={} {}".format(config_file, root_path) + command = f"flake8 --config={config_file} {root_path}" try: subprocess.check_output(command.split()) except subprocess.CalledProcessError as e: - print("command: {}".format(command)) + print(f"command: {command}") print(e.output) - self.fail("flake8 failed with return code {}.".format(e.returncode)) + self.fail(f"flake8 failed with return code {e.returncode}.") diff --git a/pykeg/core/testutils.py b/pykeg/core/testutils.py index 36ce2d23..d90bdfbd 100644 --- a/pykeg/core/testutils.py +++ b/pykeg/core/testutils.py @@ -16,7 +16,7 @@ def get_filename(f): def make_datetime(*args): if settings.USE_TZ: - return datetime.datetime(*args, tzinfo=datetime.timezone.utc) + return datetime.datetime(*args, tzinfo=datetime.UTC) else: return datetime.datetime(*args) diff --git a/pykeg/core/time_series.py b/pykeg/core/time_series.py index bcb1e223..c1be73d0 100644 --- a/pykeg/core/time_series.py +++ b/pykeg/core/time_series.py @@ -20,11 +20,11 @@ def from_string(s): time = int(time) amount = int(amount) if time < 0: - raise ValueError("Time cannot be less than zero: %s" % time) + raise ValueError(f"Time cannot be less than zero: {time}") ret.append((time, amount)) return ret def to_string(pairs): """Converts a series of (int, int) tuples to a time series string.""" - return " ".join("%i:%i" % pair for pair in pairs) + return " ".join(f"{pair[0]}:{pair[1]}" for pair in pairs) diff --git a/pykeg/core/util_test.py b/pykeg/core/util_test.py index 738c2517..eb873671 100644 --- a/pykeg/core/util_test.py +++ b/pykeg/core/util_test.py @@ -1,7 +1,5 @@ """Test for util module.""" -from builtins import str - from django.test import TestCase from packaging.version import Version diff --git a/pykeg/notification/__init__.py b/pykeg/notification/__init__.py index 951e7589..19ae4218 100644 --- a/pykeg/notification/__init__.py +++ b/pykeg/notification/__init__.py @@ -1,5 +1,4 @@ import logging -from builtins import str from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -30,8 +29,8 @@ def handle_new_system_events(events): None """ backends = get_backends() - logger.info("Handling %s event(s)" % len(events)) - logger.info("Num backends: %s" % len(backends)) + logger.info(f"Handling {len(events)} event(s)") + logger.info(f"Num backends: {len(backends)}") for event in events: handle_single_event(event, backends) @@ -40,7 +39,7 @@ def handle_single_event(event, backends): from pykeg.core import models as core_models kind = event.kind - logger.info("Processing event: %s" % event.kind) + logger.info(f"Processing event: {event.kind}") for backend in backends: backend_name = backend.name() @@ -55,14 +54,14 @@ def handle_single_event(event, backends): elif kind == event.KEG_ENDED: prefs = prefs.filter(keg_ended=True) else: - logger.info("Unknown kind: %s" % kind) + logger.info(f"Unknown kind: {kind}") prefs = [] - logger.debug("Matching prefs: %s" % str(prefs)) + logger.debug(f"Matching prefs: {str(prefs)}") for matching_pref in prefs: user = matching_pref.user if not user.is_active: - logger.info("Skipping notification for inactive user %s" % user) + logger.info(f"Skipping notification for inactive user {user}") continue - logger.info("Notifying %s for event %s" % (user, kind)) + logger.info(f"Notifying {user} for event {kind}") backend.notify(event, user) diff --git a/pykeg/notification/backends/base.py b/pykeg/notification/backends/base.py index 4b5c1919..0a69d4b8 100644 --- a/pykeg/notification/backends/base.py +++ b/pykeg/notification/backends/base.py @@ -1,9 +1,7 @@ """Base notification module.""" -from builtins import object - -class BaseNotificationBackend(object): +class BaseNotificationBackend: """Base class for notification backend implementations.""" def name(self): diff --git a/pykeg/notification/backends/email.py b/pykeg/notification/backends/email.py index 18892a1a..39d2703c 100644 --- a/pykeg/notification/backends/email.py +++ b/pykeg/notification/backends/email.py @@ -13,11 +13,11 @@ def name(cls): return "pykeg.notification.backends.email.EmailNotificationBackend" def notify(self, event, user): - logger.info("Event %s -> user %s" % (event, user)) + logger.info(f"Event {event} -> user {user}") to_address = user.email if not to_address: - logger.warning("No e-mail address available for user %s" % user) + logger.warning(f"No e-mail address available for user {user}") return context = {} @@ -45,7 +45,7 @@ def notify(self, event, user): template_name = "notification/email_keg_volume_low.html" context["url"] = event.keg.full_url() else: - logger.info("Skipping unknown event type: %s" % event.kind) + logger.info(f"Skipping unknown event type: {event.kind}") return message = build_message(to_address, template_name, context) @@ -55,5 +55,5 @@ def notify(self, event, user): self.send_message(message) def send_message(self, message): - logger.info("Sending message: %s" % message) + logger.info(f"Sending message: {message}") message.send(fail_silently=True) diff --git a/pykeg/notification/backends/email_test.py b/pykeg/notification/backends/email_test.py index 027f887f..07b8b254 100644 --- a/pykeg/notification/backends/email_test.py +++ b/pykeg/notification/backends/email_test.py @@ -40,34 +40,34 @@ def test_keg_tapped(self): msg = mail.outbox[0] self.assertEqual( - "[My Kegbot] New keg tapped: Keg %s: Unknown by Unknown" % keg.id, msg.subject + f"[My Kegbot] New keg tapped: Keg {keg.id}: Unknown by Unknown", msg.subject ) self.assertEqual(["test@example"], msg.to) self.assertEqual("test-from@example", msg.from_email) - expected_body_plain = """A new keg of Unknown by Unknown was just tapped on My Kegbot! + expected_body_plain = f"""A new keg of Unknown by Unknown was just tapped on My Kegbot! -Track it here: http://test.example.com/kegs/%s/ +Track it here: http://test.example.com/kegs/{keg.id}/ You are receiving this e-mail because you have notifications enabled -on My Kegbot. To change your settings, visit http://test.example.com/account.""" % (keg.id,) +on My Kegbot. To change your settings, visit http://test.example.com/account.""" self.assertMultiLineEqual(expected_body_plain, msg.body) - expected_body_html = """

+ expected_body_html = f"""

A new keg of Unknown by Unknown was just tapped on My Kegbot!

-Track it here. +Track it here.

You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://test.example.com/account. -

""" % (keg.id,) +

""" self.assertEqual(1, len(msg.alternatives)) self.assertMultiLineEqual(expected_body_html, msg.alternatives[0][0]) @@ -90,35 +90,33 @@ def test_session_started(self): msg = mail.outbox[0] self.assertEqual( - "[My Kegbot] A new session (session %s) has started." % (drink.session.id,), msg.subject + f"[My Kegbot] A new session (session {drink.session.id}) has started.", msg.subject ) self.assertEqual(["test@example"], msg.to) self.assertEqual("test-from@example", msg.from_email) - expected_body_plain = """A new session was just kicked off on My Kegbot. + expected_body_plain = f"""A new session was just kicked off on My Kegbot. -You can follow the session here: http://test.example.com/s/%s/ +You can follow the session here: http://test.example.com/s/{drink.session.id}/ You are receiving this e-mail because you have notifications enabled -on My Kegbot. To change your settings, visit http://test.example.com/account.""" % ( - drink.session.id, - ) +on My Kegbot. To change your settings, visit http://test.example.com/account.""" self.assertMultiLineEqual(expected_body_plain, msg.body) - expected_body_html = """

+ expected_body_html = f"""

A new session was just kicked off on My Kegbot.

-You can follow the session here. +You can follow the session here.

You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://test.example.com/account. -

""" % (drink.session.id,) +

""" self.assertEqual(1, len(msg.alternatives)) self.assertMultiLineEqual(expected_body_html, msg.alternatives[0][0]) @@ -145,39 +143,33 @@ def test_keg_volume_low(self): msg = mail.outbox[0] self.assertEqual( - "[My Kegbot] Volume low on keg %s (Unknown by Unknown)" % keg.id, msg.subject + f"[My Kegbot] Volume low on keg {keg.id} (Unknown by Unknown)", msg.subject ) self.assertEqual(["test@example"], msg.to) self.assertEqual("test-from@example", msg.from_email) - expected_body_plain = """Keg %s (Unknown by Unknown) is 15.0%% full. + expected_body_plain = f"""Keg {keg.id} (Unknown by Unknown) is 15.0% full. -See full statistics here: http://test.example.com/kegs/%s/ +See full statistics here: http://test.example.com/kegs/{keg.id}/ You are receiving this e-mail because you have notifications enabled -on My Kegbot. To change your settings, visit http://test.example.com/account.""" % ( - keg.id, - keg.id, - ) +on My Kegbot. To change your settings, visit http://test.example.com/account.""" self.assertMultiLineEqual(expected_body_plain, msg.body) - expected_body_html = """

-Keg %s (Unknown by Unknown) is 15.0%% full. + expected_body_html = f"""

+Keg {keg.id} (Unknown by Unknown) is 15.0% full.

-See full statistics here. +See full statistics here.

You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://test.example.com/account. -

""" % ( - keg.id, - keg.id, - ) +

""" self.assertEqual(1, len(msg.alternatives)) self.assertMultiLineEqual(expected_body_html, msg.alternatives[0][0]) @@ -200,39 +192,33 @@ def test_keg_ended(self): self.assertEqual(1, len(mail.outbox)) msg = mail.outbox[0] - self.assertEqual("[My Kegbot] Keg ended: Keg %s: Unknown by Unknown" % keg.id, msg.subject) + self.assertEqual(f"[My Kegbot] Keg ended: Keg {keg.id}: Unknown by Unknown", msg.subject) self.assertEqual(["test@example"], msg.to) self.assertEqual("test-from@example", msg.from_email) - expected_body_plain = """Keg %s of Unknown by Unknown was just finished on My Kegbot. + expected_body_plain = f"""Keg {keg.id} of Unknown by Unknown was just finished on My Kegbot. -See final statistics here: http://test.example.com/kegs/%s/ +See final statistics here: http://test.example.com/kegs/{keg.id}/ You are receiving this e-mail because you have notifications enabled -on My Kegbot. To change your settings, visit http://test.example.com/account.""" % ( - keg.id, - keg.id, - ) +on My Kegbot. To change your settings, visit http://test.example.com/account.""" self.assertMultiLineEqual(expected_body_plain, msg.body) - expected_body_html = """

-Keg %s (Unknown by Unknown) was just finished on + expected_body_html = f"""

+Keg {keg.id} (Unknown by Unknown) was just finished on My Kegbot.

-See final statistics here. +See final statistics here.

You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://test.example.com/account. -

""" % ( - keg.id, - keg.id, - ) +

""" self.assertEqual(1, len(msg.alternatives)) self.assertMultiLineEqual(expected_body_html, msg.alternatives[0][0]) diff --git a/pykeg/notification/forms.py b/pykeg/notification/forms.py index 290f735f..3e3bb85a 100644 --- a/pykeg/notification/forms.py +++ b/pykeg/notification/forms.py @@ -1,11 +1,9 @@ -from builtins import object - from django import forms from pykeg.core import models class NotificationSettingsForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.NotificationSettings exclude = ["user", "backend"] diff --git a/pykeg/plugin/datastore.py b/pykeg/plugin/datastore.py index 5ec56fd2..ea67e434 100644 --- a/pykeg/plugin/datastore.py +++ b/pykeg/plugin/datastore.py @@ -1,13 +1,11 @@ """Data storage interface and implementations for plugins.""" -from builtins import object - from addict import Dict from pykeg.core import models -class PluginDatastore(object): +class PluginDatastore: """Interface for plugins to persist plugin-specific data.""" def __init__(self, plugin_name): @@ -27,13 +25,13 @@ def delete(self, key): def save_form(self, form, prefix): """Helper method to save a form using the specified per-field prefix.""" for field_name, value in list(form.cleaned_data.items()): - self.set("%s:%s" % (prefix, field_name), value) + self.set(f"{prefix}:{field_name}", value) def load_form(self, form_cls, prefix, form_kwargs={}): """Helper method to load a form using the specified per-field prefix.""" data = Dict() for field_name, field in list(form_cls.base_fields.items()): - initial = self.get("%s:%s" % (prefix, field_name)) + initial = self.get(f"{prefix}:{field_name}") if initial is not None: data[field_name] = field.to_python(initial) else: @@ -72,12 +70,12 @@ def delete(self, key): class InMemoryDatastore(PluginDatastore): def __init__(self, *args, **kwargs): - super(InMemoryDatastore, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.data = {} def _keyname(self, key): """Returns the datastore-namespaced key name.""" - return "{}:{}".format(self.plugin_name, key) + return f"{self.plugin_name}:{key}" def set(self, key, value): if value is None: diff --git a/pykeg/plugin/plugin.py b/pykeg/plugin/plugin.py index d736acd8..ec714377 100644 --- a/pykeg/plugin/plugin.py +++ b/pykeg/plugin/plugin.py @@ -1,12 +1,11 @@ """Plugin interface for extending the Kegbot frontend.""" import logging -from builtins import object from pykeg.plugin.datastore import ModelDatastore -class Plugin(object): +class Plugin: """Interface class for plugins.""" NAME = None diff --git a/pykeg/plugin/util.py b/pykeg/plugin/util.py index 3b9fb26d..81dae99c 100644 --- a/pykeg/plugin/util.py +++ b/pykeg/plugin/util.py @@ -20,7 +20,7 @@ def get_plugin_class(name): module = import_module(module_path) cls = getattr(module, member_name) except (ValueError, ImportError, AttributeError) as e: - raise ImproperlyConfigured("Could not import plugin %s: %s" % (name, e)) + raise ImproperlyConfigured(f"Could not import plugin {name}: {e}") if not issubclass(cls, Plugin): raise ImproperlyConfigured("%s does not subclass plugin.Plugin", name) @@ -38,7 +38,7 @@ def get_plugins(): cls = get_plugin_class(name) plugin_obj = cls(plugin_registry=_CACHED_PLUGINS) short_name = plugin_obj.get_short_name() - assert short_name not in plugins, "Multiple plugins named {}".format(short_name) + assert short_name not in plugins, f"Multiple plugins named {short_name}" plugins[short_name] = plugin_obj _CACHED_PLUGINS = plugins return _CACHED_PLUGINS @@ -61,8 +61,8 @@ def get_account_urls(): def _to_urls(urllist, short_name): urls = [] for regex, fn, viewname in urllist: - regex = "plugin/%s/%s" % (short_name, regex) - viewname = "plugin-%s-%s" % (short_name, viewname) + regex = f"plugin/{short_name}/{regex}" + viewname = f"plugin-{short_name}-{viewname}" urls.append(re_path(regex, fn, name=viewname)) return urls diff --git a/pykeg/proto/protolib.py b/pykeg/proto/protolib.py index a96e874f..f0b4ea86 100644 --- a/pykeg/proto/protolib.py +++ b/pykeg/proto/protolib.py @@ -32,7 +32,7 @@ def ToProto(obj, full=False): elif kind in _CONVERSION_MAP: return _CONVERSION_MAP[kind](obj, full) else: - raise ValueError("Unknown object type: %s" % kind) + raise ValueError(f"Unknown object type: {kind}") def ToDict(obj, full=False): @@ -326,7 +326,7 @@ def KegTapToProto(tap, full=False): ret.meter.MergeFrom(ToProto(meter)) else: # TODO(mikey): Remove compatibility. - ret.meter_name = "unknown.%s" % tap.id + ret.meter_name = f"unknown.{tap.id}" ret.ml_per_tick = 0 toggle = tap.current_toggle() diff --git a/pykeg/proto/util.py b/pykeg/proto/util.py index 5bc4be3a..418ffb95 100644 --- a/pykeg/proto/util.py +++ b/pykeg/proto/util.py @@ -5,7 +5,7 @@ class AttrDict(dict): """ def __init__(self, mapping=None): - super(AttrDict, self).__init__() + super().__init__() if mapping is not None: for key, value in mapping.items(): self.__setitem__(key, value) @@ -13,7 +13,7 @@ def __init__(self, mapping=None): def __setitem__(self, key, value): if isinstance(value, dict): value = AttrDict(value) - super(AttrDict, self).__setitem__(key, value) + super().__setitem__(key, value) self.__dict__[key] = value # for code completion in editors def __getattr__(self, item): diff --git a/pykeg/test/plugin.py b/pykeg/test/plugin.py index 83e066ad..86069d43 100644 --- a/pykeg/test/plugin.py +++ b/pykeg/test/plugin.py @@ -14,10 +14,10 @@ def pytest_load_initial_conftests(early_config, parser, args): global TEMP_DATA_DIR TEMP_DATA_DIR = tempfile.mkdtemp() os.environ["KEGBOT_DATA_DIR"] = TEMP_DATA_DIR - logger.info("setting KEGBOT_DATA_DIR to {}".format(TEMP_DATA_DIR)) + logger.info(f"setting KEGBOT_DATA_DIR to {TEMP_DATA_DIR}") @pytest.hookimpl def pytest_sessionfinish(session, exitstatus): - logger.info("removing KEGBOT_DATA_DIR {}".format(TEMP_DATA_DIR)) + logger.info(f"removing KEGBOT_DATA_DIR {TEMP_DATA_DIR}") shutil.rmtree(TEMP_DATA_DIR) diff --git a/pykeg/util/bugreport.py b/pykeg/util/bugreport.py index d30c5b9c..adda6bb7 100644 --- a/pykeg/util/bugreport.py +++ b/pykeg/util/bugreport.py @@ -34,7 +34,7 @@ def writelog(fd, log): method = request_info.get("method") path = request_info.get("request_path") addr = request_info.get("addr") - writeline(fd, " * Request: {} {} ({})".format(method, path, addr)) + writeline(fd, f" * Request: {method} {path} ({addr})") traceback = log.get("traceback") if traceback: @@ -54,13 +54,13 @@ def get_output(cmd): .strip() ) except subprocess.CalledProcessError as e: - return "ERR ({})".format(e) + return f"ERR ({e})" def bugreport(fd): now = datetime.datetime.now() - writeline(fd, "Kegbot Server {} Bugreport".format(get_version())) - writeline(fd, "Generated {}".format(isodate.datetime_isoformat(now))) + writeline(fd, f"Kegbot Server {get_version()} Bugreport") + writeline(fd, f"Generated {isodate.datetime_isoformat(now)}") fd.write(SEPARATOR) writeline(fd, "## System info\n") @@ -101,7 +101,7 @@ def bugreport(fd): continue writelog(fd, log) except redis.RedisError as e: - writeline(fd, "ERR ({})".format(e)) + writeline(fd, f"ERR ({e})") writeline(fd, "\n") fd.write(SEPARATOR) diff --git a/pykeg/util/dbstatus.py b/pykeg/util/dbstatus.py index 09896b89..807f2cdb 100644 --- a/pykeg/util/dbstatus.py +++ b/pykeg/util/dbstatus.py @@ -16,7 +16,7 @@ class NeedMigration(DatabaseStatusError): """Thrown when a migration is needed.""" def __init__(self, migration_name): - super(NeedMigration, self).__init__('Migration "{}" not applied'.format(migration_name)) + super().__init__(f'Migration "{migration_name}" not applied') self.migration_name = migration_name diff --git a/pykeg/util/email_test.py b/pykeg/util/email_test.py index d31f5208..91109f07 100644 --- a/pykeg/util/email_test.py +++ b/pykeg/util/email_test.py @@ -1,7 +1,6 @@ """Test for email util module.""" import time -from builtins import str from django.test import TestCase diff --git a/pykeg/util/runner.py b/pykeg/util/runner.py index 71e0b5f5..1b33f825 100644 --- a/pykeg/util/runner.py +++ b/pykeg/util/runner.py @@ -49,7 +49,7 @@ def is_running(self): return self.running def add_command(self, command_name, command): - logger.debug('Adding command "{}": {}'.format(command_name, command)) + logger.debug(f'Adding command "{command_name}": {command}') if command_name in self.commands: raise ValueError("Command already installed") self.commands[command_name] = command @@ -60,7 +60,7 @@ def run(self): self.running = True self.print_startup_line() - self.logger.info("Starting commands from pid={}".format(os.getpid())) + self.logger.info(f"Starting commands from pid={os.getpid()}") dev_null_name = getattr(os, "devnull", "/dev/null") dev_null = os.open(dev_null_name, os.O_RDWR) @@ -69,7 +69,7 @@ def run(self): if sys.argv[0]: d = os.path.dirname(sys.argv[0]) if d not in path.split(":"): - path = "{}:{}".format(d, path) + path = f"{d}:{path}" user = os.environ.get("USER", "") if not user: @@ -82,27 +82,25 @@ def run(self): env["PATH"] = path env["USER"] = user - self.logger.debug("env={}".format(repr(env))) + self.logger.debug(f"env={repr(env)}") for command_name, command in list(self.commands.items()): proc = self._launch_command(command_name, command, dev_null, env) - self.logger.info("Started {} (pid={})".format(command_name, proc.pid)) + self.logger.info(f"Started {command_name} (pid={proc.pid})") self.watched_procs[command_name] = proc self.watch_commands() def watch_commands(self): - self.logger.info("Watching {} processes.".format(len(self.commands))) + self.logger.info(f"Watching {len(self.commands)} processes.") while True: abort = False for command_name, proc in list(self.watched_procs.items()): - self.logger.debug("Pinging {} (pid={})".format(command_name, proc.pid)) + self.logger.debug(f"Pinging {command_name} (pid={proc.pid})") proc.poll() if proc.returncode is not None: self.logger.info( - 'Process "{}" exited with returncode {}'.format( - command_name, proc.returncode - ) + f'Process "{command_name}" exited with returncode {proc.returncode}' ) abort = True if abort: @@ -114,16 +112,16 @@ def abort(self): self.logger.info("Abort called, killing remaining processes ...") for command_name, proc in list(self.watched_procs.items()): if proc.returncode is None: - self.logger.info("Killing {} (pid={})".format(command_name, proc.pid)) + self.logger.info(f"Killing {command_name} (pid={proc.pid})") os.killpg(proc.pid, signal.SIGTERM) for command_name, proc in list(self.watched_procs.items()): - self.logger.info("Waiting for {} to exit (pid={}) ...".format(command_name, proc.pid)) + self.logger.info(f"Waiting for {command_name} to exit (pid={proc.pid}) ...") proc.wait() self.logger.info("... done.") self.logger.info("All processes exited.") def _launch_command(self, command_name, command, dev_null, env=None): - self.logger.info("Launching command: {}: {}".format(command_name, command)) + self.logger.info(f"Launching command: {command_name}: {command}") def preexec(): # Set session id. diff --git a/pykeg/util/units.py b/pykeg/util/units.py index 3cab0c74..792f6a60 100644 --- a/pykeg/util/units.py +++ b/pykeg/util/units.py @@ -1,4 +1,3 @@ -from builtins import object from enum import Enum @@ -19,7 +18,7 @@ class UNITS(Enum): Hogshead = 238480.9434 -class Quantity(object): +class Quantity: def __init__(self, amount, units=UNITS.Milliliter, from_units=None): self._units = units self._amount = self.convert(amount, units, from_units) if from_units else amount @@ -28,10 +27,10 @@ def __init__(self, amount, units=UNITS.Milliliter, from_units=None): def fn(unit=unit): return self.ConvertTo(unit)._amount - setattr(self, "In{}s".format(unit.name), fn) + setattr(self, f"In{unit.name}s", fn) def __str__(self): - return "{} {}".format(self._amount, self._units.name) + return f"{self._amount} {self._units.name}" def __add__(self, other, subtract=False): val = 0 diff --git a/pykeg/web/account/views.py b/pykeg/web/account/views.py index c460e49f..378e334d 100644 --- a/pykeg/web/account/views.py +++ b/pykeg/web/account/views.py @@ -105,7 +105,7 @@ def notifications(request): ) message.send() messages.success( - request, "An e-mail confirmation has been sent to {}".format(new_email) + request, f"An e-mail confirmation has been sent to {new_email}" ) else: @@ -184,7 +184,7 @@ def regenerate_api_key(request): def plugin_settings(request, plugin_name): plugin = request.plugins.get(plugin_name, None) if not plugin: - raise Http404('Plugin "%s" not loaded' % plugin_name) + raise Http404(f'Plugin "{plugin_name}" not loaded') view = plugin.get_user_settings_view() if not view: diff --git a/pykeg/web/api/api_test.py b/pykeg/web/api/api_test.py index ae6f0015..f1e1cdf1 100644 --- a/pykeg/web/api/api_test.py +++ b/pykeg/web/api/api_test.py @@ -1,7 +1,5 @@ """Unittests for pykeg.web.api""" -from builtins import str - from django.core import mail from django.test import TestCase from django.test.utils import override_settings @@ -21,15 +19,15 @@ def create_site(): class BaseApiTestCase(TestCase): def get(self, subpath, data={}, follow=False, **extra): - response = self.client.get("/api/%s" % subpath, data=data, follow=follow, **extra) + response = self.client.get(f"/api/{subpath}", data=data, follow=follow, **extra) return response, kbjson.loads(response.content) def post(self, subpath, data={}, follow=False, **extra): - response = self.client.post("/api/%s" % subpath, data=data, follow=follow, **extra) + response = self.client.post(f"/api/{subpath}", data=data, follow=follow, **extra) return response, kbjson.loads(response.content) def delete(self, subpath, data={}, follow=False, **extra): - response = self.client.delete("/api/%s" % subpath, data=data, follow=follow, **extra) + response = self.client.delete(f"/api/{subpath}", data=data, follow=follow, **extra) return response, kbjson.loads(response.content) @@ -82,10 +80,10 @@ def test_defaults(self): self.assertEqual("kegboard.flow1", taps[1].meter_name) for tap in taps: - response1, data1 = self.get("taps/%s" % tap.meter_name) + response1, data1 = self.get(f"taps/{tap.meter_name}") self.assertEqual(data1.meta.result, "ok") - response2, data2 = self.get("taps/%s" % tap.id) + response2, data2 = self.get(f"taps/{tap.id}") self.assertEqual(data2.meta.result, "ok") self.assertEqual(data1, data2) diff --git a/pykeg/web/api/devicelink.py b/pykeg/web/api/devicelink.py index 548259ab..f2e4d2d6 100644 --- a/pykeg/web/api/devicelink.py +++ b/pykeg/web/api/devicelink.py @@ -24,7 +24,6 @@ """ import random -from builtins import range from django.core.cache import cache @@ -56,7 +55,7 @@ class LinkExpiredException(Exception): def _build_code(size=DEFAULT_CODE_SIZE): code = "".join(random.choice(CODE_LETTERS) for i in range(size)) mid = size // 2 - code = "{}-{}".format(code[:mid], code[mid:]) + code = f"{code[:mid]}-{code[mid:]}" return code diff --git a/pykeg/web/api/forms.py b/pykeg/web/api/forms.py index 96237d48..fc93b807 100644 --- a/pykeg/web/api/forms.py +++ b/pykeg/web/api/forms.py @@ -1,5 +1,3 @@ -from builtins import object - from django import forms from pykeg.core import models @@ -40,7 +38,7 @@ class ThermoPostForm(forms.Form): class CreateKegTapForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.KegTap fields = ("name", "notes") diff --git a/pykeg/web/api/middleware.py b/pykeg/web/api/middleware.py index b6cd77a1..0e0c70f6 100644 --- a/pykeg/web/api/middleware.py +++ b/pykeg/web/api/middleware.py @@ -1,5 +1,4 @@ import logging -from builtins import object from django.conf import settings from django.http import HttpResponse @@ -25,7 +24,7 @@ WHITELISTED_API_PATHS += getattr(settings, "KEGBOT_EXTRA_WHITELISTED_API_PATHS", ()) -class ApiRequestMiddleware(object): +class ApiRequestMiddleware: def __init__(self, get_response): self.get_response = get_response @@ -83,4 +82,4 @@ def process_exception(self, request, exception): def cache_key(request): - return "api:%s" % request.get_full_path() + return f"api:{request.get_full_path()}" diff --git a/pykeg/web/api/util.py b/pykeg/web/api/util.py index 8bd6b9af..cc2a078d 100644 --- a/pykeg/web/api/util.py +++ b/pykeg/web/api/util.py @@ -74,7 +74,7 @@ def to_json_error(e, exc_info): else: code = "ServerError" http_code = 500 - message = "An internal error occurred: %s" % str(e) + message = f"An internal error occurred: {str(e)}" result = {"error": {"code": code, "message": message}} if settings.DEBUG: result["error"]["traceback"] = "".join(traceback.format_exception(*exc_info)) @@ -90,10 +90,10 @@ def build_response(request, result_data, response_code=200): json_str = kbjson.dumps(result_data, indent=indent) if callback and validate_jsonp.is_valid_jsonp_callback_value(callback): - json_str = "%s(%s);" % (callback, json_str) + json_str = f"{callback}({json_str});" if format == "html" or (settings.DEBUG and debug): - html = "
%s
" % json_str + html = f"
{json_str}
" return HttpResponse(html, content_type="text/html", status=response_code) else: return HttpResponse(json_str, content_type="application/json", status=response_code) @@ -127,7 +127,7 @@ def wrap_exception(request, exception): exc_info = sys.exc_info() LOGGER.error( - "%s: %s" % (exception.__class__.__name__, exception), + f"{exception.__class__.__name__}: {exception}", exc_info=exc_info, extra={ "status_code": 500, diff --git a/pykeg/web/api/validate_jsonp.py b/pykeg/web/api/validate_jsonp.py index 79798f84..c45c7d6d 100644 --- a/pykeg/web/api/validate_jsonp.py +++ b/pykeg/web/api/validate_jsonp.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- - # Placed into the Public Domain by tav # origin: https://raw.github.com/tav/scripts/master/validate_jsonp.py """Validate Javascript Identifiers for use as JSON-P callback parameters.""" import re -from builtins import chr, str from unicodedata import category # ------------------------------------------------------------------------------ diff --git a/pykeg/web/api/views.py b/pykeg/web/api/views.py index f7355b1c..87f3c0fb 100644 --- a/pykeg/web/api/views.py +++ b/pykeg/web/api/views.py @@ -2,7 +2,6 @@ import datetime import logging -from builtins import str from functools import wraps from django.contrib.auth import login as auth_login @@ -350,7 +349,7 @@ def apply_since(request, query): try: since = int(since_str) return query.filter(id__gt=since) - except (ValueError, TypeError): + except ValueError, TypeError: pass return query @@ -799,7 +798,7 @@ def link_device_status(request, code): def default_handler(request): - raise Http404("Not an API endpoint: %s" % request.path[:100]) + raise Http404(f"Not an API endpoint: {request.path[:100]}") def get_tap_from_meter_name_or_404(meter_name_or_id): diff --git a/pykeg/web/auth/__init__.py b/pykeg/web/auth/__init__.py index 2a200fb7..fdada9c9 100644 --- a/pykeg/web/auth/__init__.py +++ b/pykeg/web/auth/__init__.py @@ -1,7 +1,5 @@ """Kegbot authentication backend interface.""" -from builtins import object - from django.contrib import auth from django.core.exceptions import ImproperlyConfigured @@ -10,7 +8,7 @@ def get_auth_backend(): """Returns the sole authentication backend.""" backends = auth.get_backends() if len(backends) != 1: - raise ImproperlyConfigured("Expected exactly 1 backend (found {})".format(len(backends))) + raise ImproperlyConfigured(f"Expected exactly 1 backend (found {len(backends)})") return backends[0] @@ -22,7 +20,7 @@ class UserExistsException(AuthException): """Registration error indicating this username/email is taken.""" -class AuthBackend(object): +class AuthBackend: # Django methods def authenticate(self, **credentials): diff --git a/pykeg/web/auth/local.py b/pykeg/web/auth/local.py index c7ca8e79..894ccac1 100644 --- a/pykeg/web/auth/local.py +++ b/pykeg/web/auth/local.py @@ -1,7 +1,6 @@ """Local Django authentication backend.""" import uuid -from builtins import str from django.contrib.auth.backends import ModelBackend from django.db import IntegrityError diff --git a/pykeg/web/charts/charts.py b/pykeg/web/charts/charts.py index 380876a8..4ea01d69 100644 --- a/pykeg/web/charts/charts.py +++ b/pykeg/web/charts/charts.py @@ -155,7 +155,7 @@ def chart_users_by_volume(stats, *args, **kwargs): if not username: username = "Guest" volume, units = format_volume(volume, kwargs) - label = "%s (%.1f %s)" % (username, volume, units) + label = f"{username} ({volume:.1f} {units})" data.append((label, volume)) other_vol = 0 @@ -166,7 +166,7 @@ def chart_users_by_volume(stats, *args, **kwargs): data.reverse() if other_vol: - label = "%s (%.1f)" % ("all others", other_vol) + label = "{} ({:.1f})".format("all others", other_vol) data.append((label, other_vol)) res = { diff --git a/pykeg/web/context_processors.py b/pykeg/web/context_processors.py index 8c0914e3..c55fb13c 100644 --- a/pykeg/web/context_processors.py +++ b/pykeg/web/context_processors.py @@ -15,11 +15,11 @@ def kbsite(request): sso_login_url = getattr(settings, "SSO_LOGIN_URL", "") if sso_login_url: - sso_login_url = "{}?{}".format(sso_login_url, redir) + sso_login_url = f"{sso_login_url}?{redir}" sso_logout_url = getattr(settings, "SSO_LOGOUT_URL", "") if sso_logout_url: - sso_logout_url = "{}?{}".format(sso_logout_url, redir) + sso_logout_url = f"{sso_logout_url}?{redir}" ret = { "DEBUG": settings.DEBUG, diff --git a/pykeg/web/kbregistration/forms.py b/pykeg/web/kbregistration/forms.py index 5a22cbb0..5d665819 100644 --- a/pykeg/web/kbregistration/forms.py +++ b/pykeg/web/kbregistration/forms.py @@ -1,5 +1,4 @@ import urllib.parse -from builtins import object from django import forms from django.conf import settings @@ -22,7 +21,7 @@ class KegbotRegistrationForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.User fields = ("email", "username") @@ -30,7 +29,7 @@ class Meta(object): password2 = forms.CharField(widget=forms.PasswordInput, label=_("Password (again)")) def clean(self): - super(KegbotRegistrationForm, self).clean() + super().clean() if "password1" in self.cleaned_data and "password2" in self.cleaned_data: if self.cleaned_data["password1"] != self.cleaned_data["password2"]: raise forms.ValidationError(_("The two password fields didn't match.")) diff --git a/pykeg/web/kegadmin/forms.py b/pykeg/web/kegadmin/forms.py index 61864d23..3a50cc12 100644 --- a/pykeg/web/kegadmin/forms.py +++ b/pykeg/web/kegadmin/forms.py @@ -1,5 +1,3 @@ -from builtins import object, str - from crispy_forms.bootstrap import FormActions from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Field, Layout, Submit @@ -118,14 +116,14 @@ class TapForm(forms.ModelForm): class FlowMeterModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, meter): if meter.tap: - return "{} (connected to {})".format(meter, meter.tap.name) + return f"{meter} (connected to {meter.tap.name})" else: return str(meter) class FlowToggleModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, toggle): if toggle.tap: - return "{} (connected to {})".format(toggle, toggle.tap.name) + return f"{toggle} (connected to {toggle.tap.name})" else: return str(toggle) @@ -133,7 +131,7 @@ class ThermoSensorModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, sensor): last_log = sensor.LastLog() if last_log: - return "{} (Last report: {})".format(sensor, naturaltime(last_log.time)) + return f"{sensor} (Last report: {naturaltime(last_log.time)})" else: return str(sensor) @@ -158,12 +156,12 @@ def label_from_instance(self, sensor): help_text="Optional sensor monitoring the temperature at this tap.", ) - class Meta(object): + class Meta: model = models.KegTap fields = ("name", "notes", "temperature_sensor", "sort_order") def __init__(self, *args, **kwargs): - super(TapForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.instance: self.fields["meter"].initial = self.instance.current_meter() @@ -173,7 +171,7 @@ def __init__(self, *args, **kwargs): def save(self, commit=True): if not commit: raise ValueError("TapForm does not support commit=False") - tap = super(TapForm, self).save(commit=True) + tap = super().save(commit=True) tap.connect_meter(self.cleaned_data["meter"]) tap.connect_toggle(self.cleaned_data["toggle"]) return tap @@ -312,7 +310,7 @@ def save(self): class EditKegForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.Keg fields = ( "type", @@ -347,7 +345,7 @@ class Meta(object): class GeneralSiteSettingsForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.KegbotSite fields = ( "title", @@ -374,7 +372,7 @@ class Meta(object): class LocationSiteSettingsForm(forms.ModelForm): guest_image = forms.ImageField(required=False, help_text='Custom image for the "guest" user.') - class Meta(object): + class Meta: model = models.KegbotSite fields = ( "volume_display_units", @@ -395,7 +393,7 @@ class Meta(object): class AdvancedSiteSettingsForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.KegbotSite fields = ( "session_timeout_minutes", @@ -416,7 +414,7 @@ class Meta(object): class BeverageForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.Beverage fields = ( "name", @@ -460,7 +458,7 @@ class Meta(object): class BeverageProducerForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.BeverageProducer fields = ( "name", @@ -492,7 +490,7 @@ class FindUserForm(forms.Form): class UserForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.User fields = ( "username", @@ -515,14 +513,14 @@ class Meta(object): class UserProfileForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.User fields = ("username", "display_name", "email") new_mugshot = forms.ImageField(required=False) def save(self, *args, **kwargs): - user = super(UserProfileForm, self).save(*args, **kwargs) + user = super().save(*args, **kwargs) image = self.cleaned_data.get("new_mugshot") if image: pic = models.Picture.objects.create(user=user) @@ -534,7 +532,7 @@ def save(self, *args, **kwargs): class TokenForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.AuthenticationToken fields = ( "nice_name", @@ -579,7 +577,7 @@ class DeleteTokenForm(forms.Form): class AddTokenForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.AuthenticationToken fields = ( "auth_device", @@ -740,19 +738,19 @@ def clean_address(self): class NewFlowMeterForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.FlowMeter fields = ("port_name", "ticks_per_ml", "controller") class UpdateFlowMeterForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.FlowMeter fields = ("ticks_per_ml",) class AddFlowMeterForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.FlowMeter fields = ("port_name", "ticks_per_ml", "controller") @@ -769,13 +767,13 @@ class Meta(object): class FlowToggleForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.FlowToggle fields = ("port_name", "controller") class AddFlowToggleForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.FlowToggle fields = ("port_name", "controller") @@ -801,7 +799,7 @@ class DeleteControllerForm(forms.Form): class ControllerForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.Controller fields = ("name", "model_name", "serial_number") diff --git a/pykeg/web/kegadmin/views.py b/pykeg/web/kegadmin/views.py index 845f0a31..1e8aab60 100644 --- a/pykeg/web/kegadmin/views.py +++ b/pykeg/web/kegadmin/views.py @@ -136,7 +136,7 @@ def email(request): context["settings_url"] = context["site_url"] + "/account" message = build_message(address, "notification/email_test.html", context) message.send(fail_silently=True) - messages.success(request, "E-mail successfully sent to %s" % address) + messages.success(request, f"E-mail successfully sent to {address}") context["email_configured"] = email_configured @@ -345,7 +345,7 @@ def tap_detail(request, tap_id): end_keg_form = forms.EndKegForm(request.POST) if end_keg_form.is_valid(): old_keg = tap.end_current_keg() - messages.success(request, "Keg %s was ended." % old_keg.id) + messages.success(request, f"Keg {old_keg.id} was ended.") elif "submit_record_drink" in request.POST: record_drink_form = forms.RecordDrinkForm(request.POST) @@ -353,7 +353,7 @@ def tap_detail(request, tap_id): user = record_drink_form.cleaned_data.get("user") volume_ml = record_drink_form.cleaned_data.get("volume_ml") d = models.Drink.record_drink(tap, ticks=0, username=user, volume_ml=volume_ml) - messages.success(request, "Drink %s recorded." % d.id) + messages.success(request, f"Drink {d.id} recorded.") else: messages.error(request, "Please enter a valid volume and user.") @@ -492,7 +492,7 @@ def user_list(request): user = models.User.objects.get(username=username) return redirect("kegadmin-edit-user", user.id) except models.User.DoesNotExist: - messages.error(request, 'User "%s" does not exist.' % username) + messages.error(request, f'User "{username}" does not exist.') users = models.User.objects.exclude(username="guest").order_by("-id") paginator = Paginator(users, 25) @@ -519,7 +519,7 @@ def add_user(request): instance = form.save(commit=False) instance.set_password(form.cleaned_data.get("password")) instance.save() - messages.success(request, 'User "%s" created.' % instance.username) + messages.success(request, f'User "{instance.username}" created.') return redirect("kegadmin-users") context["form"] = form return render(request, "kegadmin/add_user.html", context=context) @@ -538,7 +538,7 @@ def user_detail(request, user_id): else: edit_user.is_active = True edit_user.save() - messages.success(request, "User %s was enabled." % edit_user.username) + messages.success(request, f"User {edit_user.username} was enabled.") elif "submit_disable" in request.POST: if edit_user.is_guest(): @@ -548,7 +548,7 @@ def user_detail(request, user_id): else: edit_user.is_active = False edit_user.save() - messages.success(request, "User %s was disabled." % edit_user.username) + messages.success(request, f"User {edit_user.username} was disabled.") elif "submit_add_staff" in request.POST: if edit_user.is_staff: @@ -556,7 +556,7 @@ def user_detail(request, user_id): else: edit_user.is_staff = True edit_user.save() - messages.success(request, "User %s staff status enabled." % edit_user.username) + messages.success(request, f"User {edit_user.username} staff status enabled.") elif "submit_remove_staff" in request.POST: if edit_user.is_guest(): @@ -566,13 +566,13 @@ def user_detail(request, user_id): else: edit_user.is_staff = False edit_user.save() - messages.success(request, "User %s staff status disabled." % edit_user.username) + messages.success(request, f"User {edit_user.username} staff status disabled.") elif "submit_update_profile" in request.POST: profile_form = forms.UserProfileForm(request.POST, request.FILES, instance=edit_user) if profile_form.is_valid(): profile_form.save() - messages.success(request, "User %s e-profile updated" % edit_user.username) + messages.success(request, f"User {edit_user.username} e-profile updated") else: messages.error(request, "Unknown form submitted.") @@ -639,7 +639,7 @@ def drink_edit(request, drink_id): old_keg = drink.keg if form.is_valid(): drink.cancel_drink() - messages.success(request, "Drink %s was cancelled." % drink_id) + messages.success(request, f"Drink {drink_id} was cancelled.") return redirect(old_keg.get_absolute_url()) else: messages.error(request, "Invalid request") @@ -650,7 +650,7 @@ def drink_edit(request, drink_id): old_keg = drink.keg if form.is_valid(): drink.cancel_drink(spilled=True) - messages.success(request, "Drink %s was spilled." % drink_id) + messages.success(request, f"Drink {drink_id} was spilled.") return redirect(old_keg.get_absolute_url()) else: messages.error(request, "Invalid request") @@ -662,7 +662,7 @@ def drink_edit(request, drink_id): new_user = form.cleaned_data["user"] try: drink.reassign(new_user) - messages.success(request, "Drink %s was reassigned." % drink_id) + messages.success(request, f"Drink {drink_id} was reassigned.") except models.User.DoesNotExist: messages.error(request, "No such user") else: @@ -677,7 +677,7 @@ def drink_edit(request, drink_id): messages.warning(request, "Drink volume unchanged.") else: drink.set_volume(volume_ml) - messages.success(request, "Drink %s was updated." % drink_id) + messages.success(request, f"Drink {drink_id} was updated.") else: messages.error(request, "Please provide a valid volume.") return redirect(drink.get_absolute_url()) @@ -967,7 +967,7 @@ def autocomplete_token(request): def plugin_settings(request, plugin_name): plugin = request.plugins.get(plugin_name, None) if not plugin: - raise Http404('Plugin "%s" not loaded' % plugin_name) + raise Http404(f'Plugin "{plugin_name}" not loaded') view = plugin.get_admin_settings_view() if not view: @@ -1003,11 +1003,11 @@ def link_device(request): result = re.match("^([A-Z1-9]{3})-?([A-Z1-9]{3})$", code.upper()) if not result: messages.error('Link code is not in the form of "XXX-XXX"') - code = "{}-{}".format(result.group(1), result.group(2)) + code = f"{result.group(1)}-{result.group(2)}" try: status = devicelink.confirm_link(code) name = status.get("name", "New device") - messages.success(request, "{} linked!".format(name)) + messages.success(request, f"{name} linked!") except devicelink.LinkExpiredException: messages.error(request, "Code incorrect or expired.") return redirect("kegadmin-link-device") diff --git a/pykeg/web/kegweb/kbstorage.py b/pykeg/web/kegweb/kbstorage.py index c71c3e5d..35486306 100644 --- a/pykeg/web/kegweb/kbstorage.py +++ b/pykeg/web/kegweb/kbstorage.py @@ -25,7 +25,7 @@ def url(self, name): base_url = get_base_url() if not self.base_url.startswith(base_url): self.base_url = urllib.parse.urljoin(base_url, self.base_url) - return super(KegbotFileSystemStorage, self).url(name) + return super().url(name) if S3BotoStorage: @@ -35,4 +35,4 @@ class S3StaticStorage(S3BotoStorage): def __init__(self, *args, **kwargs): kwargs["bucket"] = S3_STATIC_BUCKET - super(S3StaticStorage, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) diff --git a/pykeg/web/kegweb/kegweb_test.py b/pykeg/web/kegweb/kegweb_test.py index 518b4c6a..396403b2 100644 --- a/pykeg/web/kegweb/kegweb_test.py +++ b/pykeg/web/kegweb/kegweb_test.py @@ -36,11 +36,11 @@ def testBasicEndpoints(self): d = models.Drink.record_drink("kegboard.flow0", ticks=100) drink_id = d.id - response = self.client.get("/d/%s" % drink_id, follow=True) - self.assertRedirects(response, "/drinks/%s/" % drink_id, status_code=301) + response = self.client.get(f"/d/{drink_id}", follow=True) + self.assertRedirects(response, f"/drinks/{drink_id}/", status_code=301) session_id = d.session.id - response = self.client.get("/s/%s" % session_id, follow=True) + response = self.client.get(f"/s/{session_id}", follow=True) self.assertRedirects(response, d.session.get_absolute_url(), status_code=301) def testShout(self): @@ -71,8 +71,8 @@ def test_privacy(self): "/kegs/": "Keg List", "/stats/": "System Stats", "/sessions/": "All Sessions", - "/kegs/{}/".format(keg.id): "Keg {}".format(keg.id), - "/drinks/{}/".format(d.id): "Drink {}".format(d.id), + f"/kegs/{keg.id}/": f"Keg {keg.id}", + f"/drinks/{d.id}/": f"Drink {d.id}", } def test_urls(expect_fail, urls=urls): diff --git a/pykeg/web/kegweb/templatetags/kegweblib.py b/pykeg/web/kegweb/templatetags/kegweblib.py index 2f4e0368..b1e2210b 100644 --- a/pykeg/web/kegweb/templatetags/kegweblib.py +++ b/pykeg/web/kegweb/templatetags/kegweblib.py @@ -1,5 +1,4 @@ import zoneinfo -from builtins import str from django.conf import settings from django.template import ( @@ -86,7 +85,7 @@ def navitem(parser, token): """{% navitem [exact] %}""" tokens = token.split_contents() if len(tokens) < 3: - raise TemplateSyntaxError("%s requires at least 3 tokens" % tokens[0]) + raise TemplateSyntaxError(f"{tokens[0]} requires at least 3 tokens") return NavitemNode(*tokens[1:]) @@ -114,7 +113,7 @@ def render(self, context): res = '<li class="active">' else: res = "<li>" - res += '<a href="%s">%s</a></li>' % (urlbase, title) + res += f'<a href="{urlbase}">{title}</a></li>' return res @@ -126,7 +125,7 @@ def timeago(parser, token): """{% timeago <timestamp> %}""" tokens = token.contents.split() if len(tokens) != 2: - raise TemplateSyntaxError("%s requires 2 tokens" % tokens[0]) + raise TemplateSyntaxError(f"{tokens[0]} requires 2 tokens") return TimeagoNode(tokens[1]) @@ -148,7 +147,7 @@ def render(self, context): iso = ts.isoformat() alt = timezone.localtime(ts).strftime("%A, %B %d, %Y %I:%M%p") - return '<abbr class="timeago" title="%s">%s</abbr>' % (iso, alt) + return f'<abbr class="timeago" title="{iso}">{alt}</abbr>' # temperature @@ -159,7 +158,7 @@ def temperature_tag(parser, token): """{% temperature <temp_c> %}""" tokens = token.contents.split() if len(tokens) < 2: - raise TemplateSyntaxError("%s requires at least 2 tokens" % tokens[0]) + raise TemplateSyntaxError(f"{tokens[0]} requires at least 2 tokens") return TemperatureNode(tokens[1]) @@ -173,7 +172,7 @@ def render(self, context): v = Variable(self.varname) try: amount = v.resolve(context) - except (VariableDoesNotExist, ValueError): + except VariableDoesNotExist, ValueError: raise amount = "unknown" @@ -194,7 +193,7 @@ def volumetag(parser, token): """{% volume <amount> %}""" tokens = token.contents.split() if len(tokens) < 2: - raise TemplateSyntaxError("%s requires at least 2 tokens" % tokens[0]) + raise TemplateSyntaxError(f"{tokens[0]} requires at least 2 tokens") return VolumeNode(tokens[1], tokens[2:]) @@ -213,7 +212,7 @@ def render(self, context): tv = Variable(self._volume_varname) try: num = float(tv.resolve(context)) - except (VariableDoesNotExist, ValueError): + except VariableDoesNotExist, ValueError: num = "unknown" unit = "mL" make_badge = "badge" in self._extra_args @@ -226,7 +225,7 @@ def format(cls, amount, units, make_badge=False): ctx = { "units": units, "amount": amount, - "title": "%s %s" % (amount, units), + "title": f"{amount} {units}", "extra_css": "badge " if make_badge else "", } return cls.TEMPLATE % ctx @@ -240,7 +239,7 @@ def drinker_name_tag(parser, token): """{% drinker_name <drink_or_user_obj> [nolink] %}""" tokens = token.contents.split() if len(tokens) < 2: - raise TemplateSyntaxError("%s requires at least 2 tokens" % tokens[0]) + raise TemplateSyntaxError(f"{tokens[0]} requires at least 2 tokens") return DrinkerNameNode(tokens[1], tokens[2:]) @@ -253,7 +252,7 @@ def render(self, context): obj = Variable(self._varname) try: obj = obj.resolve(context) - except (VariableDoesNotExist, ValueError): + except VariableDoesNotExist, ValueError: obj = None user = None @@ -266,7 +265,7 @@ def render(self, context): if "nolink" in self._extra_args: return user.get_full_name() else: - return '<a href="%s">%s</a>' % ( + return '<a href="{}">{}</a>'.format( reverse("kb-drinker", args=[user.username]), user.get_full_name(), ) @@ -324,7 +323,7 @@ def __init__(self, charttype, width, height, args): self._height = height self._args = args - self._chart_fn = getattr(charts, "chart_%s" % (self._charttype,), None) + self._chart_fn = getattr(charts, f"chart_{self._charttype}", None) def _get_chart_id(self, context): # TODO(mikey): Is there a better way to store _CHART_ID? @@ -344,7 +343,7 @@ def show_error(self, error_str): def render(self, context): if not self._chart_fn: - return self.show_error("Unknown chart type: %s" % self._charttype) + return self.show_error(f"Unknown chart type: {self._charttype}") chart_id = self._get_chart_id(context) obj = Variable(self._args[0]).resolve(context) @@ -363,7 +362,7 @@ def render(self, context): "chart": { "borderColor": "#eeeeff", "borderWidth": 0, - "renderTo": "chart-%s-container" % chart_id, + "renderTo": f"chart-{chart_id}-container", }, "credits": { "enabled": False, @@ -422,5 +421,5 @@ def volume(text, fmt="pints"): elif fmt == "halfbarrels": res = vol.InHalfBarrelKegs() else: - raise TemplateSyntaxError("Unknown volume format: %s" % fmt) + raise TemplateSyntaxError(f"Unknown volume format: {fmt}") return float(res) diff --git a/pykeg/web/kegweb/views.py b/pykeg/web/kegweb/views.py index f0de4645..bfb48bf8 100644 --- a/pykeg/web/kegweb/views.py +++ b/pykeg/web/kegweb/views.py @@ -1,7 +1,5 @@ """Kegweb main views.""" -from builtins import range, str - from django.contrib import messages from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.shortcuts import get_object_or_404, redirect, render @@ -262,7 +260,7 @@ class SessionDateDetailView(DateDetailView): def get_context_data(self, **kwargs): """Adds `stats` to the context.""" - ret = super(SessionDateDetailView, self).get_context_data(**kwargs) + ret = super().get_context_data(**kwargs) stats = ret[self.context_object_name].get_stats() ret["stats"] = stats ret["kegs"] = [models.Keg.objects.get(pk=pk) for pk in stats.get("keg_ids", [])] diff --git a/pykeg/web/middleware.py b/pykeg/web/middleware.py index b0304413..7f06cf8a 100644 --- a/pykeg/web/middleware.py +++ b/pykeg/web/middleware.py @@ -217,6 +217,4 @@ def process_view(self, request, view_func, view_args, view_kwargs): return render(request, "kegweb/members_only.html", status=401) return None - return HttpResponse( - "Server misconfigured, unknown privacy setting:%s" % privacy, status=500 - ) + return HttpResponse(f"Server misconfigured, unknown privacy setting:{privacy}", status=500) diff --git a/pykeg/web/setup_wizard/forms.py b/pykeg/web/setup_wizard/forms.py index b0092e2a..6968c2f5 100644 --- a/pykeg/web/setup_wizard/forms.py +++ b/pykeg/web/setup_wizard/forms.py @@ -1,5 +1,3 @@ -from builtins import object - from crispy_forms.bootstrap import FormActions from crispy_forms.helper import FormHelper from crispy_forms.layout import Field, Layout, Submit @@ -9,7 +7,7 @@ class MiniSiteSettingsForm(forms.ModelForm): - class Meta(object): + class Meta: model = models.KegbotSite fields = ( "title", From 536598cf9db03aedaaca7969ff7f502f2bd060a1 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Sun, 2 Aug 2026 07:21:07 +0000 Subject: [PATCH 13/30] docker: build the image with uv on python 3.14 - base python:3.14-slim; install deps with uv into the system environment (cached lockfile layer), dropping poetry and the rust/cryptography dance - keep the MySQL/Postgres/Pillow build libraries the drivers need --- Dockerfile | 58 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/Dockerfile b/Dockerfile index d2f47d05..43b7b48d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,48 +1,50 @@ -FROM python:3.10.5-bullseye +FROM python:3.14-slim RUN mkdir /app WORKDIR /app ENV SHELL=/bin/sh \ - PIP_NO_CACHE_DIR=1 \ KEGBOT_DATA_DIR=/kegbot-data \ KEGBOT_IN_DOCKER=True \ KEGBOT_ENV=debug -# Install toolchains. Mostly, image libraries that Python PIL/Pillow will require. +# Build/runtime libraries: MySQL + Postgres client headers (mysqlclient, +# psycopg2 build from source) and the image libraries Pillow needs. RUN apt-get -qq update \ - && DEBIAN_FRONTEND=noninteractive apt-get -y install \ - curl \ - libffi-dev \ + && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ + build-essential \ + pkg-config \ + default-libmysqlclient-dev \ + libpq-dev \ + libjpeg-dev \ libfreetype6-dev \ - libfribidi-dev \ - libharfbuzz-dev \ - libjpeg-turbo-progs \ - libjpeg62-turbo-dev \ liblcms2-dev \ libopenjp2-7-dev \ - libtiff5-dev \ + libtiff-dev \ libwebp-dev \ - libssl-dev \ zlib1g-dev \ - && rm -rf /var/lib/apt/lists/* \ - && python -m pip install -U pip \ - # The cryptography build requires rust, which adds >1GB to the image. \ - # Install it only to install cryptography, then remove it. \ - && curl https://sh.rustup.rs -sSf | sh -s -- -y \ - && PATH=/root/.cargo/bin:$PATH pip install cryptography \ - && rm -rf /root/.rustup /root/.cargo \ - && pip install poetry \ - && rm -rf /root/.cache + && rm -rf /var/lib/apt/lists/* -# Install python dependencies. -COPY pyproject.toml poetry.lock ./ -ADD pykeg/__init__.py ./pykeg/ -RUN poetry config virtualenvs.create false && poetry install -n +# uv, configured to install into the system Python (no venv). +COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /uvx /bin/ +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_NO_DEV=1 \ + UV_PYTHON_DOWNLOADS=0 \ + UV_PROJECT_ENVIRONMENT=/usr/local + +# Install dependencies first (cached) using only the lockfile + manifest. +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --locked --no-install-project # Install the app itself. -ADD bin /usr/local/sbin/ -ADD pykeg ./pykeg +COPY bin /usr/local/sbin/ +COPY pykeg ./pykeg +COPY pyproject.toml uv.lock ./ +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked # Collect static files. Use fake versions of required env variables # since they're not relevant at this step. @@ -55,7 +57,7 @@ RUN DATABASE_URL=mysql:// \ ARG GIT_SHORT_SHA="unknown" ARG VERSION="unknown" ARG BUILD_DATE="unknown" -RUN echo "GIT_SHORT_SHA=${GIT_SHORT_SHA}\nVERSION=${VERSION}\nBUILD_DATE=${BUILD_DATE}" > /etc/kegbot-version +RUN printf "GIT_SHORT_SHA=%s\nVERSION=%s\nBUILD_DATE=%s\n" "${GIT_SHORT_SHA}" "${VERSION}" "${BUILD_DATE}" > /etc/kegbot-version VOLUME ["/kegbot-data"] From 1fdb6378288dd3da22befa8dbe44b6cba92e1553 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Sun, 2 Aug 2026 07:21:07 +0000 Subject: [PATCH 14/30] actions: modernize CI for uv and python 3.14 - pybuild: uv + python 3.14, a sqlite/mysql/postgres matrix with a redis service, run migrations + collectstatic + pytest + ruff; trigger on main - docker: trigger on main, drop the armv7 target and its qemu/tmpfs workaround, refresh action versions and use $GITHUB_OUTPUT --- .github/workflows/docker.yml | 51 ++++++++------------ .github/workflows/pybuild.yml | 87 +++++++++++++++++++++++++++-------- 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f0f52f59..1eb9186d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,36 +20,26 @@ jobs: runs-on: ubuntu-latest steps: - - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v6 - - - name: Prepare + - name: Prepare id: prepare run: | if [[ $GITHUB_REF == refs/tags/* ]]; then TAG=${GITHUB_REF#refs/tags/} - echo ::set-output name=tag_name::${TAG} - echo ::set-output name=version::${TAG%-*} + echo "tag_name=${TAG}" >> $GITHUB_OUTPUT + echo "version=${TAG%-*}" >> $GITHUB_OUTPUT else - echo ::set-output name=version::snapshot + echo "version=snapshot" >> $GITHUB_OUTPUT fi - echo ::set-output name=build_date::$(date -u +'%Y-%m-%dT%H:%M:%SZ') - - # Work around an armv7 -> qemu bug caused during rust compilation. Yeah.. See repo. - - name: Run Docker on tmpfs - uses: JonasAlfredsson/docker-on-tmpfs@v1 - with: - tmpfs_size: 8 - swap_size: 8 - swap_location: '/mnt/swapfile' + echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # Set up the tag(s) we will build/push. The `metadata-action` action has # a bunch of rules and a little language for specifying this. This should @@ -62,11 +52,10 @@ jobs: # Merge to main "latest" latest # Merge tag <tag> v1.2.3b1 # Merge semver tag <semver> 1.2.3 - # Merge semver tag <major>.<minor> 1.2 - - - name: Set up docker tag names + # Merge semver tag <major>.<minor> 1.2 + - name: Set up docker tag names id: docker_meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -77,14 +66,14 @@ jobs: type=semver,pattern={{major}}.{{minor}} - name: Build locally - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: context: . cache-from: type=gha cache-to: type=gha,mode=max push: false tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + labels: ${{ steps.docker_meta.outputs.labels }} build-args: | BUILD_DATE=${{ steps.prepare.outputs.build_date }} GIT_SHORT_SHA=${GITHUB_SHA::8} @@ -92,26 +81,25 @@ jobs: platforms: | linux/amd64 linux/arm64 - linux/arm/v7 - name: Log in to container registry - if: ${{ '${{ secrets.GITHUB_TOKEN }}' != '' }} - uses: docker/login-action@v2 + if: ${{ github.event_name != 'pull_request' }} + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push to container registry - if: ${{ '${{ secrets.GITHUB_TOKEN }}' != '' }} - uses: docker/build-push-action@v3 + if: ${{ github.event_name != 'pull_request' }} + uses: docker/build-push-action@v6 with: context: . cache-from: type=gha cache-to: type=gha,mode=max push: true tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + labels: ${{ steps.docker_meta.outputs.labels }} build-args: | BUILD_DATE=${{ steps.prepare.outputs.build_date }} GIT_SHORT_SHA=${GITHUB_SHA::8} @@ -119,4 +107,3 @@ jobs: platforms: | linux/amd64 linux/arm64 - linux/arm/v7 diff --git a/.github/workflows/pybuild.yml b/.github/workflows/pybuild.yml index 148b5d70..397a9451 100644 --- a/.github/workflows/pybuild.yml +++ b/.github/workflows/pybuild.yml @@ -2,32 +2,83 @@ name: Python Build & Test on: push: - branches: - - main + branches: [main] pull_request: - branches: - - main + branches: [main] jobs: - py_build_and_test: + test: + name: 'Build and test (${{ matrix.database }})' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + database: [sqlite, mysql, postgres] + include: + - database: sqlite + database_url: sqlite:///test-db.sqlite + - database: mysql + database_url: mysql://root:cipass@127.0.0.1/kegbot_ci + - database: postgres + database_url: postgres://postgres:cipass@127.0.0.1/kegbot_ci + + services: + mysql: + image: ${{ matrix.database == 'mysql' && 'mysql:8' || '' }} + env: + MYSQL_ROOT_PASSWORD: cipass + MYSQL_DATABASE: kegbot_ci + ports: + - 3306:3306 + options: --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 10 + + postgres: + image: ${{ matrix.database == 'postgres' && 'postgres:16' || '' }} + env: + POSTGRES_PASSWORD: cipass + POSTGRES_DB: kegbot_ci + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 10 + + redis: + image: redis + ports: + - 6379:6379 + + env: + DATABASE_URL: ${{ matrix.database_url }} + REDIS_URL: redis://localhost:6379/0 + KEGBOT_SECRET_KEY: changeme + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 - - name: Setup Python - uses: actions/setup-python@v1 + - name: Set up Python 3.14 + uses: actions/setup-python@v6 with: - python-version: "3.10" + python-version: '3.14' - - name: Install dependencies + - name: Install system libraries run: | - pip install poetry docker-compose - poetry install -n + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + default-libmysqlclient-dev libpq-dev pkg-config - - name: Run mysql & redis - run: | - docker-compose -f testdata/test-docker-compose.yml up -d - sleep 10 && ./bin/wait-for-container.sh testdata_mysql_1 + - name: Install dependencies + run: uv sync --all-groups + + - name: Run migrations + run: uv run bin/kegbot migrate --noinput + + - name: Collect static files + run: uv run bin/kegbot collectstatic --noinput -v 0 + + - name: Run tests + run: uv run pytest - - name: pytest - run: DATABASE_URL=mysql://root:changeme@127.0.0.1:3306/kegbot_dev REDIS_URL=redis:// KEGBOT_SECRET_KEY=changeme poetry run pytest + - name: Run lint checks + run: uv run ruff check From 9cf5eb3c5035037a63ed58ce48b862adbb91729f Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Sun, 2 Aug 2026 07:24:00 +0000 Subject: [PATCH 15/30] misc: update changelog - add upgrade notes under a "Current version (unreleased)" heading - set the package version to a clear pre-release marker (2.0.0.dev0) - keep the demo-site server_version current in the v2 API tests so the version bump doesn't trip the "upgrade required" gate --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ pykeg/api/tests.py | 5 +++++ pyproject.toml | 2 +- uv.lock | 2 +- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e107e9..afc33575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,32 @@ You can [view the published changelog here](https://docs.kegbot.org/projects/kegbot-server/en/latest/releases/changelog.html). +## Current version (unreleased) + +A modernization release. The runtime, framework, and toolchain were all brought +up to date. + +### Highlights + +- **Python 3.14** is now required (was 3.10). +- **Django 5.2 LTS** (was 3.2). +- Web server switched from **gunicorn/gevent** to **waitress**. +- Packaging moved from **Poetry** to **uv**; linting/formatting moved to **ruff**; + pre-flight checks run via **pre-commit**. +- protobuf upgraded to the 6.x series. +- Docker image rebuilt on `python:3.14-slim` with uv. + +### Upgrade notes (for existing installs) + +- **Everyone is logged out once.** Sessions now use the JSON serializer (Django + removed the pickle serializer), so existing session cookies are invalidated on + upgrade. Users simply log in again. +- **Background jobs enqueue on database commit.** Stats/notification jobs are now + handed to the worker only after the surrounding DB transaction commits. Ensure + `run_workers` is running (unchanged) to process them. +- **`run_gunicorn` was removed.** Use `kegbot run_server` (now waitress). Tune + with `--waitress_options` (e.g. `--threads=8`); `$PORT` is still honored. +- The Docker image no longer publishes a `linux/arm/v7` variant (amd64 + arm64 + only). +- The legacy gflags-based Python API client was removed from the server package; + it lives in the separate kegbot-api project. diff --git a/pykeg/api/tests.py b/pykeg/api/tests.py index a3e4414b..d2b4b1fb 100644 --- a/pykeg/api/tests.py +++ b/pykeg/api/tests.py @@ -4,6 +4,7 @@ from rest_framework.test import APIClient from pykeg.core import models +from pykeg.core.util import get_version class ApiClient: @@ -35,6 +36,10 @@ class V2ApiTestCase(TestCase): def setUp(self): self.client = ApiClient() self.site = models.KegbotSite.objects.all().first() + # The fixture bakes in an older server_version; keep it current so the + # "upgrade required" gate doesn't intercept API requests under test. + self.site.server_version = get_version() + self.site.save() self.user = models.User.objects.all().first() self.api_key = models.ApiKey.objects.get_or_create(user=self.user)[0] diff --git a/pyproject.toml b/pyproject.toml index a2d70ae4..4af76677 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kegbot" -version = "1.3.0" +version = "2.0.0.dev0" description = "Kegbot Server" authors = [{ name = "The Kegbot Project contributors" }] license = "MIT" diff --git a/uv.lock b/uv.lock index 6d9a554b..ade23ff5 100644 --- a/uv.lock +++ b/uv.lock @@ -389,7 +389,7 @@ wheels = [ [[package]] name = "kegbot" -version = "1.3.0" +version = "2.0.0.dev0" source = { editable = "." } dependencies = [ { name = "addict" }, From c7fff4f247bcfa8c3e929ec97ba41424a82a9716 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 04:18:52 +0000 Subject: [PATCH 16/30] deps: declare packaging as a runtime dependency - pykeg imports packaging.version directly, but it previously arrived only as a transitive dev dependency; the no-dev docker image exposed the gap - verified by import-walking every non-test module and running collectstatic in a --no-dev environment --- pyproject.toml | 1 + uv.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4af76677..cfcbbe6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "isodate", "mysqlclient", "oauthlib", + "packaging", "Pillow", "pilkit", "protobuf>=5,<7", diff --git a/uv.lock b/uv.lock index ade23ff5..10dec555 100644 --- a/uv.lock +++ b/uv.lock @@ -412,6 +412,7 @@ dependencies = [ { name = "isodate" }, { name = "mysqlclient" }, { name = "oauthlib" }, + { name = "packaging" }, { name = "pilkit" }, { name = "pillow" }, { name = "protobuf" }, @@ -457,6 +458,7 @@ requires-dist = [ { name = "isodate" }, { name = "mysqlclient" }, { name = "oauthlib" }, + { name = "packaging" }, { name = "pilkit" }, { name = "pillow" }, { name = "protobuf", specifier = ">=5,<7" }, From e2bbc3ff86906b015e8b4504c674766f8ba4c5a4 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 04:21:26 +0000 Subject: [PATCH 17/30] test: fix postgres-order-dependent meter/toggle api tests - FlowMeter/FlowToggle have no Meta.ordering, and the tests indexed a lazy unordered queryset that re-executes after the disconnect/connect updates; postgres relocates updated rows, reshuffling the result - pin the tap's own meter/toggle via the OneToOne relation instead --- pykeg/web/api/api_test.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pykeg/web/api/api_test.py b/pykeg/web/api/api_test.py index f1e1cdf1..62bcd799 100644 --- a/pykeg/web/api/api_test.py +++ b/pykeg/web/api/api_test.py @@ -335,8 +335,8 @@ def test_controller_data(self): def test_add_remove_meters(self): response, data = self.get(f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") - meters = models.FlowMeter.objects.all() - self.assertEqual(data.object.meter.id, meters[0].id) + meter = models.FlowMeter.objects.get(tap=self.tap) + self.assertEqual(data.object.meter.id, meter.id) original_data = data response, data = self.post( @@ -348,18 +348,17 @@ def test_add_remove_meters(self): response, data = self.post( f"taps/{self.tap.id}/connect-meter", HTTP_X_KEGBOT_API_KEY=self.apikey.key, - data={"meter": meters[0].id}, + data={"meter": meter.id}, ) self.assertEqual(data.meta.result, "ok") - meters = models.FlowMeter.objects.all() - self.assertEqual(data.object.meter.id, meters[0].id) + self.assertEqual(data.object.meter.id, meter.id) self.assertEqual(original_data, data) def test_add_remove_toggles(self): response, data = self.get(f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") - toggles = models.FlowToggle.objects.all() - self.assertEqual(data.object.toggle.id, toggles[0].id) + toggle = models.FlowToggle.objects.get(tap=self.tap) + self.assertEqual(data.object.toggle.id, toggle.id) response, data = self.post( f"taps/{self.tap.id}/disconnect-toggle", HTTP_X_KEGBOT_API_KEY=self.apikey.key @@ -370,12 +369,12 @@ def test_add_remove_toggles(self): response, data = self.post( f"taps/{self.tap.id}/connect-toggle", HTTP_X_KEGBOT_API_KEY=self.apikey.key, - data={"toggle": toggles[0].id}, + data={"toggle": toggle.id}, ) response, data = self.get(f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") self.assertIsNotNone(data.object.get("toggle")) - self.assertEqual(data.object.toggle.id, toggles[0].id) + self.assertEqual(data.object.toggle.id, toggle.id) def test_get_version(self): response, data = self.get("version") From cb82fa5ad94cf0421377f3af89e73fa3ab984f37 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 04:31:04 +0000 Subject: [PATCH 18/30] actions: bump docker actions to node 24 majors - clears the "Node.js 20 is deprecated" annotations; all five bumps are pure runtime updates with no input changes we rely on - includes login-action, which only runs on push so hadn't warned yet --- .github/workflows/docker.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1eb9186d..67d5fed1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,10 +36,10 @@ jobs: echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 # Set up the tag(s) we will build/push. The `metadata-action` action has # a bunch of rules and a little language for specifying this. This should @@ -55,7 +55,7 @@ jobs: # Merge semver tag <major>.<minor> 1.2 - name: Set up docker tag names id: docker_meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -66,7 +66,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} - name: Build locally - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . cache-from: type=gha @@ -84,7 +84,7 @@ jobs: - name: Log in to container registry if: ${{ github.event_name != 'pull_request' }} - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -92,7 +92,7 @@ jobs: - name: Build and push to container registry if: ${{ github.event_name != 'pull_request' }} - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . cache-from: type=gha From 4efc196dbdc23dcb64b9ec767c311a3892fe5400 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 04:41:47 +0000 Subject: [PATCH 19/30] actions: tag "latest" from releases, not main - docker "latest" now follows the newest non-prerelease semver tag (via metadata-action `flavor: latest=auto`) instead of every merge to main - merges to main publish only "dev", so sites pulling latest stay on the newest tagged release --- .github/workflows/docker.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 67d5fed1..3ee2b16c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -49,18 +49,22 @@ jobs: # When What Example # =================== ============================= ============= # All runs "dev" dev - # Merge to main "latest" latest # Merge tag <tag> v1.2.3b1 # Merge semver tag <semver> 1.2.3 # Merge semver tag <major>.<minor> 1.2 + # Merge semver tag "latest" (non-prerelease) latest + # + # Note that "latest" follows tagged releases (via `flavor: latest=auto`), + # NOT merges to main; main merges publish only "dev". - name: Set up docker tag names id: docker_meta uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + latest=auto tags: | type=raw,value=dev - type=raw,value=latest,enable={{is_default_branch}} type=ref,event=tag type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} From fa7c0efbc8c7c31498506f073a2641ecc7a1511c Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 05:07:00 +0000 Subject: [PATCH 20/30] deps: drop unused packages, move fakeredis to dev - remove future, httplib2, oauthlib, requests-oauthlib (nothing imports them) and the direct pilkit pin (still present transitively via django-imagekit) - fakeredis is only imported under pytest, so it belongs in the dev group - freezegun stays a runtime dep: generate_fixtures uses it outside tests - re-verified the no-dev (docker) environment via import-walk + collectstatic --- docs/rtd-requirements.txt | 1 - pyproject.toml | 13 +- uv.lock | 460 ++++++++++++++++++++++++++++++++++---- 3 files changed, 419 insertions(+), 55 deletions(-) delete mode 100644 docs/rtd-requirements.txt diff --git a/docs/rtd-requirements.txt b/docs/rtd-requirements.txt deleted file mode 100644 index ca5e0a10..00000000 --- a/docs/rtd-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -sphinx-issues==1.2.0 diff --git a/pyproject.toml b/pyproject.toml index cfcbbe6b..1cd80f43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,22 +19,16 @@ dependencies = [ "django-rq>=3,<5", "djangorestframework>=3.16,<4", "Django>=5.2,<5.3", - "fakeredis", "freezegun>=1.2.1,<2", - "future>=0.18.2,<0.19", - "httplib2", "isodate", "mysqlclient", - "oauthlib", "packaging", "Pillow", - "pilkit", "protobuf>=5,<7", "psycopg2", "PyYAML", "redis", "requests", - "requests-oauthlib", "rq>=2,<3", "waitress>=3,<4", "whitenoise", @@ -44,7 +38,14 @@ dependencies = [ repository = "https://github.com/Kegbot/kegbot-server" [dependency-groups] +docs = [ + "furo", + "sphinx", + "sphinx-autobuild", + "sphinx-issues", +] dev = [ + "fakeredis", "pre-commit", "pytest", "pytest-django", diff --git a/uv.lock b/uv.lock index 10dec555..38e7cc24 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,18 @@ version = 1 revision = 3 requires-python = ">=3.14, <4" +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + [[package]] name = "addict" version = "2.4.0" @@ -11,6 +23,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" }, ] +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + [[package]] name = "asgiref" version = "3.12.1" @@ -20,6 +53,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" }, ] +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +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 = "beautifulsoup4" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, +] + [[package]] name = "certifi" version = "2026.7.22" @@ -287,6 +342,15 @@ 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 = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + [[package]] name = "fakeredis" version = "2.37.0" @@ -322,21 +386,28 @@ wheels = [ ] [[package]] -name = "future" -version = "0.18.3" +name = "furo" +version = "2025.12.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/2e/cf6accf7415237d6faeeebdc7832023c90e0282aa16fd3263db0eb4715ec/future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307", size = 840896, upload-time = "2023-01-13T03:15:17.607Z" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "beautifulsoup4" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "sphinx-basic-ng" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, +] [[package]] -name = "httplib2" -version = "0.32.0" +name = "h11" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyparsing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/84/f5/ccf58de92d61e3ad921119668f54ed36ca1d0cf5dcc5c1657dfb164fd78b/httplib2-0.32.0.tar.gz", hash = "sha256:48a0ef30a42db65d8f3399045e1d09ab0ba66e3b9efc360d07f80ea55d286025", size = 254283, upload-time = "2026-06-26T10:13:56.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/a0/550eec327e5f5c7b732531c489f5307efec41f047b0d703bd4ca1e5ad2db/httplib2-0.32.0-py3-none-any.whl", hash = "sha256:dc6705cacdf3fb0a2aba7629fa33c90fd93e30035db0c157325826be177e4816", size = 93148, upload-time = "2026-06-26T10:13:54.985Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] @@ -369,6 +440,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -387,6 +467,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "kegbot" version = "2.0.0.dev0" @@ -405,22 +497,16 @@ dependencies = [ { name = "django-registration" }, { name = "django-rq" }, { name = "djangorestframework" }, - { name = "fakeredis" }, { name = "freezegun" }, - { name = "future" }, - { name = "httplib2" }, { name = "isodate" }, { name = "mysqlclient" }, - { name = "oauthlib" }, { name = "packaging" }, - { name = "pilkit" }, { name = "pillow" }, { name = "protobuf" }, { name = "psycopg2" }, { name = "pyyaml" }, { name = "redis" }, { name = "requests" }, - { name = "requests-oauthlib" }, { name = "rq" }, { name = "waitress" }, { name = "whitenoise" }, @@ -428,6 +514,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "fakeredis" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-django" }, @@ -435,6 +522,12 @@ dev = [ { name = "ruff" }, { name = "vcrpy" }, ] +docs = [ + { name = "furo" }, + { name = "sphinx" }, + { name = "sphinx-autobuild" }, + { name = "sphinx-issues" }, +] [package.metadata] requires-dist = [ @@ -451,22 +544,16 @@ requires-dist = [ { name = "django-registration", specifier = ">=5.1" }, { name = "django-rq", specifier = ">=3,<5" }, { name = "djangorestframework", specifier = ">=3.16,<4" }, - { name = "fakeredis" }, { name = "freezegun", specifier = ">=1.2.1,<2" }, - { name = "future", specifier = ">=0.18.2,<0.19" }, - { name = "httplib2" }, { name = "isodate" }, { name = "mysqlclient" }, - { name = "oauthlib" }, { name = "packaging" }, - { name = "pilkit" }, { name = "pillow" }, { name = "protobuf", specifier = ">=5,<7" }, { name = "psycopg2" }, { name = "pyyaml" }, { name = "redis" }, { name = "requests" }, - { name = "requests-oauthlib" }, { name = "rq", specifier = ">=2,<3" }, { name = "waitress", specifier = ">=3,<4" }, { name = "whitenoise" }, @@ -474,6 +561,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "fakeredis" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-django" }, @@ -481,6 +569,42 @@ dev = [ { name = "ruff" }, { name = "vcrpy" }, ] +docs = [ + { name = "furo" }, + { name = "sphinx" }, + { name = "sphinx-autobuild" }, + { name = "sphinx-issues" }, +] + +[[package]] +name = "markupsafe" +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/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] [[package]] name = "mysqlclient" @@ -501,15 +625,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] -[[package]] -name = "oauthlib" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, -] - [[package]] name = "packaging" version = "26.2" @@ -648,15 +763,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "pyparsing" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, -] - [[package]] name = "pyreadline3" version = "3.5.6" @@ -781,16 +887,12 @@ wheels = [ ] [[package]] -name = "requests-oauthlib" -version = "2.0.0" +name = "roman-numerals" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] [[package]] @@ -841,6 +943,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -850,6 +961,138 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] +[[package]] +name = "soupsieve" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/38/e12680bbe6b4f8f3d17adcaf38d26850aa756c85cf4a80e79fc12a018fe8/soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba", size = 122261, upload-time = "2026-07-21T16:57:17.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/2c/437fe806897c2d6cfdc3ee43a18da8bf8e568530a4ae9bac781541ca9896/soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c", size = 37404, upload-time = "2026-07-21T16:57:16.421Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-autobuild" +version = "2025.8.25" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "sphinx" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "watchfiles" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, +] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, +] + +[[package]] +name = "sphinx-issues" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/4e/c9a46b19c90d4d3cdcbcaf0e4d392b09e5b41ec75b134bc5a1aa192be94f/sphinx_issues-6.0.0.tar.gz", hash = "sha256:f40f2c71cecb2068c7f06a53825778b674d58d8395b4ea60551a36164d2988e3", size = 15230, upload-time = "2026-03-13T17:23:32.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/8a/28333d222ac31539aa0d818db0790077e5a6a408f933b4bf64f7d3440ffc/sphinx_issues-6.0.0-py3-none-any.whl", hash = "sha256:c7ed2915059526d02022733985a7712d4b2b64a707e16b98294ed9758e64df4f", size = 8362, upload-time = "2026-03-13T17:23:31.031Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + [[package]] name = "sqlparse" version = "0.5.5" @@ -859,6 +1102,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, ] +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + [[package]] name = "typing-extensions" version = "4.16.0" @@ -886,6 +1141,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] +[[package]] +name = "uvicorn" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/18/ccce41535dee1be77735592bd19965f3972c82e07ee703d324709496b716/uvicorn-0.52.1.tar.gz", hash = "sha256:112ec661814189acbccd3f7b86460147cc065fc92c0821afa78918780e4354dd", size = 100571, upload-time = "2026-08-01T18:19:30.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d5/68e6e9bca63c0badf67002890a46d3784c958de45b65e1275ec583ca1f06/uvicorn-0.52.1-py3-none-any.whl", hash = "sha256:e4403f9d93188cf9d1088e9f40e3acd12630e2df8675316704379a7fc20fff6a", size = 79859, upload-time = "2026-08-01T18:19:29.294Z" }, +] + [[package]] name = "vcrpy" version = "8.3.0" @@ -923,6 +1191,102 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e", size = 56232, upload-time = "2024-11-16T20:02:33.858Z" }, ] +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, +] + +[[package]] +name = "websockets" +version = "17.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/96/e01084f83a64bcb3a27994bd0cb0db68ff29d9c6707fae37ec19b18ba990/websockets-17.0.1.tar.gz", hash = "sha256:5baa9bc0dfbae8c507e51c8cf1b6d4628086f7a87bbd3a9952bd5f035451f1cc", size = 183298, upload-time = "2026-07-31T11:31:27.665Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/dc/cadab608924ac605647031472fb1f8792d7d4ea07565ba1899ec42028e0d/websockets-17.0.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:53b90c00bc6201ab6695c7ff51a04d0e425514c37515e9eeecd2c1b978ac6c0e", size = 212640, upload-time = "2026-07-31T11:30:22.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/7a/b034d13ca181211bbd58bb50835cb196a7784cd505b5a2079d4d03374f9f/websockets-17.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f33a649bfcb8312524173cc4bbafa7dbb236e18eee9aa31a1d324ca0ddda28c", size = 210332, upload-time = "2026-07-31T11:30:23.608Z" }, + { url = "https://files.pythonhosted.org/packages/2f/4d/943ede39b53744768edf1ed84a3f9401527388228a3d6c1249c02c3d6bd7/websockets-17.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cddc675ec31bca65473321f9a9794e488b43b3b8de5d02c8ef4810c5d5792163", size = 210546, upload-time = "2026-07-31T11:30:24.932Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/88dc159d2ae66743c669443246243f28d873b0c5e58271b8cc1ca0440334/websockets-17.0.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b3ff0ad440ad52dda64138f16895f66403f40192365e39b1010e889f289746b0", size = 219928, upload-time = "2026-07-31T11:30:26.221Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f2/ff27eaefa15851a5cf7f004ab827a022bf2d6632cb520f89cb100db7e84b/websockets-17.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:72d7f2a5aeb4e82daa4ee18f125b4277f427033359be5c745ad709608446cc2c", size = 220279, upload-time = "2026-07-31T11:30:27.49Z" }, + { url = "https://files.pythonhosted.org/packages/19/2e/a5166149f363d2449c1cb2dde6486a245521979509d53b87a09f3e79662b/websockets-17.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6fd88365da261c53d3e943fb37e0d0721b9cde119f6b2e3fc84369b6ab234d63", size = 221525, upload-time = "2026-07-31T11:30:28.872Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/f46931269b3ff3bde65d27c65ddb22f9bb8ce92ac2c6c4df0910128f6219/websockets-17.0.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ab9f962a5b64a5c3c845d556b7dc4e6fb683f7b67179f8205e814bb2e0213ffe", size = 222897, upload-time = "2026-07-31T11:30:30.164Z" }, + { url = "https://files.pythonhosted.org/packages/42/f4/deccf3439f35df953ec35e13fe07986821c5f1ab5785d69614283bdb9034/websockets-17.0.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c07f145d0b9e90cbd96035f31fb79199aef4da1872854e36ebeb258e3d57594", size = 222129, upload-time = "2026-07-31T11:30:31.489Z" }, + { url = "https://files.pythonhosted.org/packages/35/a5/e1b57a59da92ade37fd021567a17b518ea8267b28e5530075844cdb525fe/websockets-17.0.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9f7747d3daa41a11f25f7cca5dc988fc51da97b311bed4c9d843860f79779283", size = 220875, upload-time = "2026-07-31T11:30:32.805Z" }, + { url = "https://files.pythonhosted.org/packages/f0/30/e7d0889c790a854156de424575fd67af79ddbaed9ff3157ae863dfd1c1dc/websockets-17.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2abb1ba0a5133b7d2ef3c1c9f4b0c1e8a101012dce0b594ab2b2888d9a64820e", size = 218160, upload-time = "2026-07-31T11:30:34.512Z" }, + { url = "https://files.pythonhosted.org/packages/09/2e/43db785d6ed9ae7594fae7b62bbc9cb4dfee2b015e06a1005f1e5ce283b6/websockets-17.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f3fd9a1f87f8f0f3f8e9f9bd0195f7516562d13f5b178db8c5784d1f60b60bed", size = 220951, upload-time = "2026-07-31T11:30:35.806Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f5/4ac3cab3d5e8a830657a822f64a8910e3803229c6783e59c3fd9a3487427/websockets-17.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2bc14b481e05e331811108daa1aeb41a5e237a5564ef2f02ec5a356a0f102f78", size = 219460, upload-time = "2026-07-31T11:30:37.273Z" }, + { url = "https://files.pythonhosted.org/packages/03/0e/c3a4020673ffc17c82cf1a467835038a196a555d3b4f2a50f0f063cf8ccc/websockets-17.0.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57d2ee9b24b404ce75f3814f92073c0ed88106c950148d2427fe8d25ca254d1f", size = 220248, upload-time = "2026-07-31T11:30:38.527Z" }, + { url = "https://files.pythonhosted.org/packages/7d/87/e47a6a278cc1dfade38444c893ce18322943c25d4b780a74450d9d164be1/websockets-17.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1b363bfd72a52c0658a3154a4cff219f15a474b35a235057d38853bf151acce7", size = 221421, upload-time = "2026-07-31T11:30:39.879Z" }, + { url = "https://files.pythonhosted.org/packages/da/8f/473d5fc4e3836e375b0233c6ef26777e6e5e3f7bfc84ccd524eae4090ed5/websockets-17.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:10b1587c599fa0f2c89154587c80e0fda98ade6c9fa8c0260a2823fb1800b685", size = 218975, upload-time = "2026-07-31T11:30:41.192Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b9/819ec2dcdf69031d7e9cab11247f3a6ff9bbc8c7c53ada1dbcb9055b227b/websockets-17.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d7d72843691f50b91127c50688df10cb72ec6f4c4b1d7e2c11ab33b16acf8e51", size = 219925, upload-time = "2026-07-31T11:30:42.524Z" }, + { url = "https://files.pythonhosted.org/packages/85/b9/6c0da301f6118502e079cf92f4e864adf28e56b3f8c0f6085076ced7b876/websockets-17.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90973a3a00f23afdfd1c9b06fb84289bf0220f247ef8a62501a1967c7af54f7b", size = 220218, upload-time = "2026-07-31T11:30:44.04Z" }, + { url = "https://files.pythonhosted.org/packages/55/f9/cba32dc9dd856565263d6272f594255bd0e2781deb8cd982c026a54760ad/websockets-17.0.1-cp314-cp314-win32.whl", hash = "sha256:599b03beb77633bffc095334338fad79cafc2b01fbd58953838130a9ae967d7b", size = 212626, upload-time = "2026-07-31T11:30:45.579Z" }, + { url = "https://files.pythonhosted.org/packages/fa/95/91cdd8c192287d7ea741f37cf7d64fdc1a14410f06f73805e428a1a590af/websockets-17.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:81ce19c6046ace11da7001781be7317bb1dc389f399af4b2ed962190f76f9add", size = 212969, upload-time = "2026-07-31T11:30:46.983Z" }, + { url = "https://files.pythonhosted.org/packages/8e/fd/8c98a1e431960661c5769ab1a4dd66494e87ab02d791cc79e51e0d9a289f/websockets-17.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:efe0ae052a8d023b87198921e8a7ce1dc7768816bcd2fbc20df171ac73a04891", size = 212850, upload-time = "2026-07-31T11:30:48.304Z" }, + { url = "https://files.pythonhosted.org/packages/13/c1/142f5186ee7dc3beee0426b998a79e223e067b7689afcaa95890d64aa800/websockets-17.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ab56439c9f74c52770690c7b2f616b3bf775cb3920453ee355ac765c032d8bbf", size = 212967, upload-time = "2026-07-31T11:30:49.688Z" }, + { url = "https://files.pythonhosted.org/packages/02/de/4b03ed316c9dee180365286c298219809ff247be39beeaaf9958b21167ab/websockets-17.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:20a92f78ac8250984ed459faa9ca48c285adbfc0038ddc3fdac6046990a9c9ed", size = 210504, upload-time = "2026-07-31T11:30:50.987Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d8/ad2b3e8f867e1e8cac3077e2f33ffb60b71bd763d6cfc71bd916f113c3bf/websockets-17.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6a434e59962a4fb9016bea327e1d14d6cd67670ecfb8942b4f4a0c24036634ce", size = 210702, upload-time = "2026-07-31T11:30:52.261Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/7a77a82ce9d6f831c07b176da3942f7e71acd0f115f3ecdb1d00a040eb01/websockets-17.0.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2503c7e2a5049a12d5dac917a46d5d52591283a766165b8176bb167560421b38", size = 220290, upload-time = "2026-07-31T11:30:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/f5/af/43c3e3c3ea7ba4693c2181743f3221957df28bededadcbd9fc8a0661bde0/websockets-17.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:28012a54510fe8301bb893ef143cec30a2780a2d3bc20b7bbdf4379d7a63945d", size = 220573, upload-time = "2026-07-31T11:30:54.966Z" }, + { url = "https://files.pythonhosted.org/packages/1f/bd/ed48eca15725743ee7e2dc172e15c61de29e85ec98dace7b14257f366836/websockets-17.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22bd00f8bae2bccdb5dbe41e20f58ba44ca9fff0b4b561aaf39099c35da762ed", size = 221747, upload-time = "2026-07-31T11:30:56.762Z" }, + { url = "https://files.pythonhosted.org/packages/99/50/838deb7937a8225c4925dd4a977eafea473fabf444178a99de0bc7e92bb0/websockets-17.0.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e98ec9ec61cce5bc4b8b218322ad090b0994eb060bb04da704c62ef0a3d864e6", size = 223891, upload-time = "2026-07-31T11:30:58.127Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e9/657fb70c6eb6bcd01adfa5d2b06496e9911e1c1a8813d353b8c00f7591cd/websockets-17.0.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e387adb0c692c6b5571bdeafc8ac9d1901ea30f10309134780b16ecd35e6605", size = 222317, upload-time = "2026-07-31T11:30:59.416Z" }, + { url = "https://files.pythonhosted.org/packages/07/4c/82cb722afa5428fed981331210c4c07600570db01bb1620579f655b5adaf/websockets-17.0.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd1470d2c53fe53269bf5619da7725d30dd9b9693f1689f7a85eab8dea734442", size = 221047, upload-time = "2026-07-31T11:31:00.757Z" }, + { url = "https://files.pythonhosted.org/packages/90/84/bd6d67d6bc65f0de0cb50de55dab42f256a9876a351c4736522eb168fda0/websockets-17.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:884af729b8ab50486acd94d9768c2b60914bf39b579ebba0a5cb73bfdfd61fd2", size = 218626, upload-time = "2026-07-31T11:31:02.48Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/6a372c8553976f0d8f97f5115826ed47e34b3be6b8bf0d0249af249a7416/websockets-17.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a60fa1a25cca1bcc2bf87b8d6be37a741f0a3239fb5e9cfb7a37173b68ffcf87", size = 221299, upload-time = "2026-07-31T11:31:03.795Z" }, + { url = "https://files.pythonhosted.org/packages/bc/31/f966e8472337974f74d788b3ef6c6f3b8b9a5f201efd16a843c91d269fa5/websockets-17.0.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:e8208f2729cba030ff872a92064c97584eeb9502f53d32a05a0f05d5a17ca6c6", size = 219789, upload-time = "2026-07-31T11:31:05.08Z" }, + { url = "https://files.pythonhosted.org/packages/57/34/404e83a6cc7b0efcac810b7041bffd72ff76900e6fd0aa45a26c92fb2ffe/websockets-17.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:54cdcaa56f5d3eafd57058f0fa4a3de93a310b43a3c4699f06efc4c0bd054a5a", size = 220678, upload-time = "2026-07-31T11:31:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/8946188c2a68d67251859b589a3634918cf7867bf0b891347a5ecaa43d30/websockets-17.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4d41c0a1d47a478bc432b3b9068097bee1ce0c5b19327ea6f75c2ab34ab1f2fb", size = 221697, upload-time = "2026-07-31T11:31:08.023Z" }, + { url = "https://files.pythonhosted.org/packages/04/16/ee73fc2083a2938ac6209f4ec804960496835b20a0068dbcfe8424957c04/websockets-17.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f991247276797d0c61ab7770bc9791eadc16f683b4d83517f624932adc1a8bab", size = 219390, upload-time = "2026-07-31T11:31:09.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/6a/d5f88033c69932af6cdaa72da62516ade47c257e3bf69f4c0ba5f40e12a2/websockets-17.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:733e3cc7171fa1b899edbe725ef9382d0e960657dc1fd933f3281ae910c01dab", size = 220161, upload-time = "2026-07-31T11:31:10.915Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/6183dd2c0370287ecf4afe0bb33aca364208e5e7b0e1a286adcaecc0c78b/websockets-17.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:810cb3fb5fa6e447216f4e82d9a85cb8aed0929ae3538153ddfe8a6e3121a58d", size = 220591, upload-time = "2026-07-31T11:31:12.289Z" }, + { url = "https://files.pythonhosted.org/packages/26/93/70f6516d85b9744f7eac224c4b1b9ef4e84133f80b53be02080cb1c3e663/websockets-17.0.1-cp314-cp314t-win32.whl", hash = "sha256:17ac37716c0244e82c9e384c41653c090b1864c6610224ca3857e7f7b58fce10", size = 212755, upload-time = "2026-07-31T11:31:13.883Z" }, + { url = "https://files.pythonhosted.org/packages/f1/2c/9d9c1da5a7ea9af307b386d25f64d1dead4729644198d2b92e36db5dfd41/websockets-17.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bb31f42ea095ea826463c770829aa188a86c9a5c976b1467cbbf583c811de833", size = 213094, upload-time = "2026-07-31T11:31:15.367Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/a585e7573e128070605d003b5544729bcd58d9756c7e99d550818ca4b916/websockets-17.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dbfae8e75b342e31fc6fd1a8bbb393b7cbb91d6cfd581650300a94381e7b7e2b", size = 213009, upload-time = "2026-07-31T11:31:16.776Z" }, + { url = "https://files.pythonhosted.org/packages/09/ce/3929538b2b9918f5eee623fbf3346893973191f6df93f19bbda097bd7bb7/websockets-17.0.1-py3-none-any.whl", hash = "sha256:c6be9cba65c65cc76dfa3d4619e359ff02a4476c74e179b215236c11a0b32345", size = 206718, upload-time = "2026-07-31T11:31:26.037Z" }, +] + [[package]] name = "whitenoise" version = "6.12.0" From d6c291faa9359181bd4d7bdd9881ac75078f61a6 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 05:07:22 +0000 Subject: [PATCH 21/30] docs: fix the read the docs build for uv - the old config still ran poetry against the deleted poetry.lock - add a `docs` dependency group (sphinx, furo, sphinx-issues, sphinx-autobuild) and build via uv on RTD - use stock furo from PyPI instead of the dormant Kegbot fork; the only theme options in use (light/dark logo) are standard, and the build passes with -W - docs use no autodoc, so the project (and its C-extension deps) is not installed for the docs build --- .readthedocs.yaml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6fe48fff..9f789331 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,12 +5,16 @@ # Required version: 2 +# Install the docs toolchain with uv (from the `docs` dependency group) and +# build with sphinx. The project itself is not installed: the docs use no +# autodoc, so the heavyweight runtime deps (mysqlclient, etc.) are unneeded. build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: '3.10' - jobs: - post_install: - - pip install poetry - - poetry config virtualenvs.create false - - poetry install + python: '3.13' + commands: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - uv sync --only-group docs --frozen + - uv run --no-sync sphinx-build -T -b html docs/source $READTHEDOCS_OUTPUT/html From 34cb81f7d5b5d0f4069d8e72b13ee8621486356b Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 06:07:27 +0000 Subject: [PATCH 22/30] commands: fix argument parsing in restore, create_api_key, rename_user - these used the pre-1.10 optparse style and could not receive positional args on modern Django --- pykeg/core/management/commands/create_api_key.py | 11 +++++------ pykeg/core/management/commands/rename_user.py | 12 ++++++------ pykeg/core/management/commands/restore.py | 10 +++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/pykeg/core/management/commands/create_api_key.py b/pykeg/core/management/commands/create_api_key.py index 95b9ae88..83f29d8d 100644 --- a/pykeg/core/management/commands/create_api_key.py +++ b/pykeg/core/management/commands/create_api_key.py @@ -1,15 +1,14 @@ -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from pykeg.core import models class Command(BaseCommand): - args = "<description>" help = "Creates an API key with the given description." - def handle(self, *args, **options): - if len(args) < 1: - raise CommandError("Must specify description") + def add_arguments(self, parser): + parser.add_argument("description") - key = models.ApiKey.objects.create(description=args[0]) + def handle(self, *args, **options): + key = models.ApiKey.objects.create(description=options["description"]) print(key.key) diff --git a/pykeg/core/management/commands/rename_user.py b/pykeg/core/management/commands/rename_user.py index 1f7721a0..493234f6 100644 --- a/pykeg/core/management/commands/rename_user.py +++ b/pykeg/core/management/commands/rename_user.py @@ -5,15 +5,15 @@ class Command(BaseCommand): - args = "<from> <to>" help = "Renames user from <from> to <to>." - def handle(self, *args, **options): - if len(args) < 2: - raise CommandError("Must specify <from> and <to>") + def add_arguments(self, parser): + parser.add_argument("from_username", metavar="from") + parser.add_argument("to_username", metavar="to") - from_username = args[0] - to_username = args[1] + def handle(self, *args, **options): + from_username = options["from_username"] + to_username = options["to_username"] if from_username == "guest": raise CommandError("Cannot rename the guest user.") diff --git a/pykeg/core/management/commands/restore.py b/pykeg/core/management/commands/restore.py index 6c8412f8..4c0e1a76 100644 --- a/pykeg/core/management/commands/restore.py +++ b/pykeg/core/management/commands/restore.py @@ -8,12 +8,12 @@ class Command(BaseCommand): help = "Restores a zipfile backup of the current Kegbot system." - args = "<zipfile>" + + def add_arguments(self, parser): + parser.add_argument("zipfile") def handle(self, *args, **options): - if len(args) != 1: - raise CommandError("Must provide a zipfile path") - backup_path = os.path.normpath(os.path.expanduser(args[0])) + backup_path = os.path.normpath(os.path.expanduser(options["zipfile"])) if not os.path.exists(backup_path): raise CommandError(f"Archive does not exist: {backup_path}") @@ -25,6 +25,6 @@ def handle(self, *args, **options): sys.exit(1) except backup.BackupError as e: sys.stderr.write("Error: ") - sys.stderr.write(e) + sys.stderr.write(str(e)) sys.stderr.write("\n") sys.exit(1) From c2a72dd6b0c022fd243a633cdca7e87529464f14 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 06:07:27 +0000 Subject: [PATCH 23/30] backup: support restoring legacy format-1 (v1.1.x) backups - restore dispatches on backup_format; format 1 loads the per-table fixture dumps through the ORM into a freshly migrated schema, then finalizes version + stats, so no separate upgrade step is needed - media/zip files are now opened in binary mode (restore was broken for images on python 3) - verified end-to-end against a real 2014 v1.1.0 site backup --- .gitignore | 4 +- CHANGELOG.md | 3 + pykeg/backup/backup.py | 24 +++++- pykeg/backup/legacy.py | 147 ++++++++++++++++++++++++++++++++ pykeg/backup/legacy_test.py | 165 ++++++++++++++++++++++++++++++++++++ 5 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 pykeg/backup/legacy.py create mode 100644 pykeg/backup/legacy_test.py diff --git a/.gitignore b/.gitignore index 189d604b..6158c700 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ docs/build ### pykeg build dist -test-db.sqlite \ No newline at end of file +test-db.sqlite + +/tmp diff --git a/CHANGELOG.md b/CHANGELOG.md index afc33575..8f45cd11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ up to date. pre-flight checks run via **pre-commit**. - protobuf upgraded to the 6.x series. - Docker image rebuilt on `python:3.14-slim` with uv. +- **Very old backups can now be restored directly.** `kegbot restore` accepts + legacy format-1 backups (created by Kegbot v1.1.x) and upgrades their data in + one step; no intermediate 1.2/1.3 install is needed. ### Upgrade notes (for existing installs) diff --git a/pykeg/backup/backup.py b/pykeg/backup/backup.py index 5e43d6a6..2963afc9 100644 --- a/pykeg/backup/backup.py +++ b/pykeg/backup/backup.py @@ -34,6 +34,7 @@ import isodate from django.conf import settings from django.core.files.storage import default_storage +from django.core.management import call_command from django.db import connection, transaction from django.utils import timezone from django.utils.text import slugify @@ -41,6 +42,7 @@ from pykeg.core.util import get_version from pykeg.util import kbjson +from . import legacy from .exceptions import AlreadyInstalledError, BackupError, InvalidBackup logger = logging.getLogger(__name__) @@ -199,7 +201,7 @@ def backup(storage=default_storage, include_media=True): sha1.update(chunk) digest = sha1.hexdigest() saved_zip_name = os.path.join(BACKUPS_DIRNAME, f"{backup_name}-{digest}.zip") - with open(temp_zip) as temp_zip_file: + with open(temp_zip, "rb") as temp_zip_file: ret = storage.save(saved_zip_name, temp_zip_file) return ret finally: @@ -222,7 +224,7 @@ def verify_backup_directory(backup_dir): if not os.path.exists(metadata_file): raise InvalidBackup("Metadata file does not exist") - if not os.path.exists(metadata_file): + if not os.path.exists(os.path.join(backup_dir, SQL_FILENAME)): raise InvalidBackup("SQL dumpfile does not exist") metadata = kbjson.loads(open(metadata_file).read()) @@ -233,6 +235,13 @@ def verify_backup_directory(backup_dir): return metadata +def read_metadata_from_directory(backup_dir): + metadata_file = os.path.join(backup_dir, METADATA_FILENAME) + if not os.path.exists(metadata_file): + raise InvalidBackup("Metadata file does not exist") + return kbjson.loads(open(metadata_file).read()) + + def restore_media(backup_dir, storage): media_dir = os.path.join(backup_dir, "media") @@ -240,7 +249,7 @@ def restore_media(backup_dir, storage): for filename in files: full_path = os.path.join(dirname, filename) rel_path = os.path.relpath(full_path, media_dir) - with open(full_path) as data: + with open(full_path, "rb") as data: logger.debug(f"+++ Restoring file {rel_path}") storage.save(rel_path, data) @@ -248,6 +257,15 @@ def restore_media(backup_dir, storage): def restore_from_directory(backup_dir, storage=default_storage): logger.info(f"Restoring from {backup_dir} ...") + if read_metadata_from_directory(backup_dir).get(META_BACKUP_FORMAT) == 1: + # Legacy (v1.1.x) backups have no SQL dump; data is restored + # through the ORM into a freshly migrated database. + call_command("migrate", interactive=False, verbosity=0) + legacy.restore_data(backup_dir) + restore_media(backup_dir, storage) + logger.info("Restore completed successfully.") + return + if db_impl.is_installed(): raise AlreadyInstalledError("You must erase this system before restoring.") diff --git a/pykeg/backup/legacy.py b/pykeg/backup/legacy.py new file mode 100644 index 00000000..e0a0d00e --- /dev/null +++ b/pykeg/backup/legacy.py @@ -0,0 +1,147 @@ +"""Restore support for legacy format-1 backups (Kegbot v1.1.x). + +Format-1 backups store per-table Django fixture JSON under `tables/` +rather than a SQL dump. The v1.1 restore procedure loaded those fixtures +through the ORM into a freshly-migrated database, so the modern +equivalent does the same: migrate to the current schema, transform the +fixtures across the (small) v1.1 -> v1.2/1.3 model changes, and load +them with `loaddata`. + +Because data lands directly in the current schema, a completed legacy +restore needs no separate `kegbot upgrade`: the stored server version is +set to the current version and stats are regenerated as a final step. +""" + +import glob +import json +import logging +import os +import tempfile + +from django.apps import apps +from django.core.management import call_command +from django.core.management.color import no_style +from django.db import connection + +from .exceptions import InvalidBackup + +logger = logging.getLogger(__name__) + +LEGACY_BACKUP_FORMAT = 1 +TABLES_DIRNAME = "tables" + +# Tables that existed in v1.1 but have no modern equivalent. +IGNORED_TABLES = {"south_migrationhistory"} + + +def load_table_dumps(backup_dir): + """Reads all fixture files from a format-1 backup's `tables/` dir.""" + tables_dir = os.path.join(backup_dir, TABLES_DIRNAME) + if not os.path.isdir(tables_dir): + raise InvalidBackup("Backup does not contain a tables/ directory") + + tables = {} + for path in sorted(glob.glob(os.path.join(tables_dir, "*.json"))): + name = os.path.basename(path)[: -len(".json")] + with open(path) as f: + tables[name] = json.load(f) + return tables + + +def transform_tables(tables): + """Transforms v1.1-era fixtures for the current models. + + Applies the same changes the core 0002-0004 migrations performed on + live data: + + - keg: finished/online -> status ("on_tap" when a tap's current_keg + points at the keg, else "finished" or "available") + - kegtap: description -> notes + - kegbotsite: drop check_for_updates + - drinkingsession: timezone <- site timezone + + Returns a flat list of serialized objects, ready for `loaddata`. + """ + tables = {k: v for k, v in tables.items() if k not in IGNORED_TABLES} + + site_tz = "UTC" + for row in tables.get("core_kegbotsite", []): + fields = row["fields"] + site_tz = fields.get("timezone", site_tz) + fields.pop("check_for_updates", None) + + on_tap_kegs = { + row["fields"]["current_keg"] + for row in tables.get("core_kegtap", []) + if row["fields"].get("current_keg") + } + for row in tables.get("core_keg", []): + fields = row["fields"] + finished = fields.pop("finished", False) + fields.pop("online", None) + if row["pk"] in on_tap_kegs: + fields["status"] = "on_tap" + elif finished: + fields["status"] = "finished" + else: + fields["status"] = "available" + + for row in tables.get("core_kegtap", []): + fields = row["fields"] + if "description" in fields: + fields["notes"] = fields.pop("description") + + for row in tables.get("core_drinkingsession", []): + row["fields"].setdefault("timezone", site_tz) + + objects = [] + for rows in tables.values(): + objects.extend(rows) + return objects + + +def load_data(objects): + """Loads transformed fixture objects into the current database.""" + # loaddata (rather than direct deserialization) so foreign key checks + # are deferred for the duration of the load. + with tempfile.TemporaryDirectory() as tmpdir: + fixture_path = os.path.join(tmpdir, "legacy_backup.json") + with open(fixture_path, "w") as f: + json.dump(objects, f) + call_command("loaddata", fixture_path, verbosity=0) + + # Rows were inserted with explicit pks; realign auto-increment + # sequences (needed on postgres; a no-op elsewhere). + sequence_sql = connection.ops.sequence_reset_sql(no_style(), apps.get_models()) + if sequence_sql: + with connection.cursor() as cursor: + for statement in sequence_sql: + cursor.execute(statement) + + +def restore_data(backup_dir): + """Restores a format-1 backup's data into a migrated, empty database. + + The caller is responsible for schema (migrations) and media. + """ + from pykeg.core import models, stats + from pykeg.core.util import get_version + + from .exceptions import AlreadyInstalledError + + if models.KegbotSite.objects.exists(): + raise AlreadyInstalledError("You must erase this system before restoring.") + + tables = load_table_dumps(backup_dir) + objects = transform_tables(tables) + logger.info(f"Loading {len(objects)} records from legacy backup ...") + load_data(objects) + + # Data now lives in the current schema; no separate upgrade step remains. + site = models.KegbotSite.get() + site.server_version = get_version() + site.save(update_fields=["server_version"]) + + logger.info("Regenerating stats ...") + stats.invalidate_all() + stats.rebuild_from_id(0) diff --git a/pykeg/backup/legacy_test.py b/pykeg/backup/legacy_test.py new file mode 100644 index 00000000..76fe85b3 --- /dev/null +++ b/pykeg/backup/legacy_test.py @@ -0,0 +1,165 @@ +"""Tests for legacy (format-1) backup restore.""" + +import json +import os +import shutil +import tempfile +import unittest + +from django.apps import apps +from django.core import serializers +from django.core.files.storage import FileSystemStorage +from django.core.management import call_command +from django.test import TransactionTestCase + +from pykeg.backup import backup, legacy +from pykeg.core import defaults, models +from pykeg.core.util import get_version + +METER_NAME = "kegboard.flow0" +FAKE_BEER_NAME = "Testy Beer" +FAKE_BREWER_NAME = "Unittest Brewery" +FAKE_BEER_STYLE = "Test-Driven Pale Ale" + +MEDIA_CONTENTS = b"\xff\xd8\xff fake jpeg bytes" + + +class TransformTablesTestCase(unittest.TestCase): + def test_keg_status(self): + tables = { + "core_kegtap": [{"pk": 1, "model": "core.kegtap", "fields": {"current_keg": 2}}], + "core_keg": [ + {"pk": 1, "model": "core.keg", "fields": {"finished": True, "online": False}}, + {"pk": 2, "model": "core.keg", "fields": {"finished": False, "online": True}}, + {"pk": 3, "model": "core.keg", "fields": {"finished": False, "online": False}}, + ], + } + objects = legacy.transform_tables(tables) + statuses = {o["pk"]: o["fields"]["status"] for o in objects if o["model"] == "core.keg"} + self.assertEqual({1: "finished", 2: "on_tap", 3: "available"}, statuses) + for o in objects: + self.assertNotIn("finished", o["fields"]) + self.assertNotIn("online", o["fields"]) + + def test_kegtap_description_renamed(self): + tables = { + "core_kegtap": [ + {"pk": 1, "model": "core.kegtap", "fields": {"description": "spare tap"}} + ] + } + (tap,) = legacy.transform_tables(tables) + self.assertEqual("spare tap", tap["fields"]["notes"]) + self.assertNotIn("description", tap["fields"]) + + def test_site_and_session_fields(self): + tables = { + "core_kegbotsite": [ + { + "pk": 1, + "model": "core.kegbotsite", + "fields": {"check_for_updates": True, "timezone": "US/Pacific"}, + } + ], + "core_drinkingsession": [ + {"pk": 1, "model": "core.drinkingsession", "fields": {}}, + ], + "south_migrationhistory": [ + {"pk": 1, "model": "south.migrationhistory", "fields": {}}, + ], + } + objects = legacy.transform_tables(tables) + by_model = {o["model"]: o for o in objects} + self.assertNotIn("south.migrationhistory", by_model) + self.assertNotIn("check_for_updates", by_model["core.kegbotsite"]["fields"]) + self.assertEqual("US/Pacific", by_model["core.drinkingsession"]["fields"]["timezone"]) + + +class LegacyRestoreTestCase(TransactionTestCase): + def setUp(self): + defaults.set_defaults(set_is_setup=True, create_controller=True) + models.Keg.start_keg( + METER_NAME, + beverage_name=FAKE_BEER_NAME, + beverage_type="beer", + producer_name=FAKE_BREWER_NAME, + style_name=FAKE_BEER_STYLE, + ) + models.Drink.record_drink(METER_NAME, ticks=2200, volume_ml=1000) + models.Drink.record_drink(METER_NAME, ticks=1100, volume_ml=500) + + self.backup_dir = tempfile.mkdtemp() + self.media_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.backup_dir) + self.addCleanup(shutil.rmtree, self.media_dir) + + def build_format1_backup(self): + """Serializes current site data devolved to v1.1 fixture shape.""" + tables = {} + for model in apps.get_app_config("core").get_models(): + rows = json.loads(serializers.serialize("json", model.objects.all())) + if rows: + tables[model._meta.db_table] = rows + + for row in tables["core_kegbotsite"]: + row["fields"]["check_for_updates"] = True + row["fields"]["server_version"] = "1.1" + for row in tables["core_kegtap"]: + row["fields"]["description"] = row["fields"].pop("notes", "") + for row in tables["core_keg"]: + status = row["fields"].pop("status") + row["fields"]["finished"] = status == "finished" + row["fields"]["online"] = status == "on_tap" + for row in tables.get("core_drinkingsession", []): + row["fields"].pop("timezone", None) + + tables_dir = os.path.join(self.backup_dir, legacy.TABLES_DIRNAME) + os.makedirs(tables_dir) + for name, rows in tables.items(): + with open(os.path.join(tables_dir, f"{name}.json"), "w") as f: + json.dump(rows, f) + + with open(os.path.join(self.backup_dir, "metadata.json"), "w") as f: + json.dump({"backup_format": 1, "server_version": "1.1.0"}, f) + + pics_dir = os.path.join(self.backup_dir, "media", "pics") + os.makedirs(pics_dir) + with open(os.path.join(pics_dir, "test.jpg"), "wb") as f: + f.write(MEDIA_CONTENTS) + + return tables + + def test_restore(self): + tables = self.build_format1_backup() + expected_counts = {name: len(rows) for name, rows in tables.items()} + + call_command("flush", interactive=False, verbosity=0) + self.assertFalse(models.KegbotSite.objects.exists()) + + storage = FileSystemStorage(location=self.media_dir) + backup.restore_from_directory(self.backup_dir, storage=storage) + + site = models.KegbotSite.get() + self.assertEqual(get_version(), site.server_version) + + for model in apps.get_app_config("core").get_models(): + expected = expected_counts.get(model._meta.db_table, 0) + self.assertEqual( + expected, model.objects.count(), f"count mismatch for {model._meta.db_table}" + ) + + keg = models.Keg.objects.get() + self.assertEqual(models.Keg.STATUS_ON_TAP, keg.status) + self.assertEqual(2, models.Drink.objects.count()) + + with storage.open("pics/test.jpg") as f: + self.assertEqual(MEDIA_CONTENTS, f.read()) + + # Sequences realigned: creating new records must not collide. + models.Drink.record_drink(METER_NAME, ticks=1100, volume_ml=500) + self.assertEqual(3, models.Drink.objects.count()) + + def test_restore_requires_empty_site(self): + self.build_format1_backup() + storage = FileSystemStorage(location=self.media_dir) + with self.assertRaises(backup.AlreadyInstalledError): + backup.restore_from_directory(self.backup_dir, storage=storage) From ef37c0b91936bdc29652b8984e04e01596ae0ddd Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 06:10:10 +0000 Subject: [PATCH 24/30] backup: only reset sequences for restored models - apps.get_models() includes models without tables (django_rq dashboard), which broke the legacy restore test on postgres --- pykeg/backup/legacy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pykeg/backup/legacy.py b/pykeg/backup/legacy.py index e0a0d00e..72c77aae 100644 --- a/pykeg/backup/legacy.py +++ b/pykeg/backup/legacy.py @@ -111,8 +111,10 @@ def load_data(objects): call_command("loaddata", fixture_path, verbosity=0) # Rows were inserted with explicit pks; realign auto-increment - # sequences (needed on postgres; a no-op elsewhere). - sequence_sql = connection.ops.sequence_reset_sql(no_style(), apps.get_models()) + # sequences (needed on postgres; a no-op elsewhere). Only the loaded + # models: other apps may have models without tables. + loaded_models = {apps.get_model(o["model"]) for o in objects} + sequence_sql = connection.ops.sequence_reset_sql(no_style(), sorted(loaded_models, key=str)) if sequence_sql: with connection.cursor() as cursor: for statement in sequence_sql: From 5d2d16675bbda858c1d13d4f44f3598c21d5d903 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 06:30:41 +0000 Subject: [PATCH 25/30] backup: fix python 3 bitrot in the backup creation path - media files and the zip were written through text-mode handles - os.path.join was passed a tuple when recursing into media subdirs - is_installed compared str against bytes output on mysql and postgres - postgres restore/erase never passed PGPASSWORD - the backup command imported get_storage_class, removed in Django 5.1 None of this was covered by tests (backup_test.py is skipped); the new legacy-upgrade CI workflow exercises it against real databases. --- pykeg/backup/backup.py | 8 ++++---- pykeg/backup/mysql.py | 2 +- pykeg/backup/postgres.py | 6 +++--- pykeg/core/management/commands/backup.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pykeg/backup/backup.py b/pykeg/backup/backup.py index 2963afc9..77bdec7c 100644 --- a/pykeg/backup/backup.py +++ b/pykeg/backup/backup.py @@ -125,13 +125,13 @@ def add_files(storage, dirname, destdir): output_dirname = os.path.dirname(output_filename) if not os.path.exists(output_dirname): os.makedirs(output_dirname) - with storage.open(full_filename, "r") as srcfile: - with open(output_filename, "w") as dstfile: + with storage.open(full_filename, "rb") as srcfile: + with open(output_filename, "wb") as dstfile: logger.debug(f"+++ Creating {output_filename}") shutil.copyfileobj(srcfile, dstfile) metadata[META_NUM_MEDIA_FILES] += 1 for subdir in subdirs: - add_files(storage, os.path.join((dirname, subdir)), destdir) + add_files(storage, os.path.join(dirname, subdir), destdir) if include_media: destdir = os.path.join(backup_dir, "media") @@ -164,7 +164,7 @@ def add_files(storage, dirname, destdir): def create_backup_zip(backup_dir, backup_name): """Creates a zipfile based on a tree created with `create_backup_tree()`.""" zipfile_fileno, zipfile_path = tempfile.mkstemp() - zipfile_fd = os.fdopen(zipfile_fileno, "w") + zipfile_fd = os.fdopen(zipfile_fileno, "wb") zf = zipfile.ZipFile(zipfile_fd, "w") for dirname, subdirs, files in os.walk(backup_dir): diff --git a/pykeg/backup/mysql.py b/pykeg/backup/mysql.py index 2bb36a9f..75482f7e 100644 --- a/pykeg/backup/mysql.py +++ b/pykeg/backup/mysql.py @@ -40,7 +40,7 @@ def is_installed(): cmd = " ".join(args) logger.info(f"command: {cmd}") - output = subprocess.check_output(cmd, shell=True) + output = subprocess.check_output(cmd, shell=True, text=True) logger.info(f"result: {output}") return "core_kegbotsite" in output diff --git a/pykeg/backup/postgres.py b/pykeg/backup/postgres.py index de387e99..355877ed 100644 --- a/pykeg/backup/postgres.py +++ b/pykeg/backup/postgres.py @@ -41,7 +41,7 @@ def is_installed(): args += ["-qt", "-c \"select * from pg_tables where schemaname='public';\"", PARAMS["db"]] cmd = " ".join(args) logger.info(cmd) - output = subprocess.check_output(cmd, env=DEFAULT_ENV, shell=True) + output = subprocess.check_output(cmd, env=DEFAULT_ENV, shell=True, text=True) return "core_" in output @@ -58,7 +58,7 @@ def restore(input_fd): args.append(PARAMS["db"]) cmd = " ".join(args) logger.info(cmd) - return subprocess.check_call(cmd, stdin=input_fd, shell=True) + return subprocess.check_call(cmd, stdin=input_fd, env=DEFAULT_ENV, shell=True) def erase(): @@ -66,4 +66,4 @@ def erase(): args += [PARAMS["db"], "-c 'drop schema public cascade; create schema public;'"] cmd = " ".join(args) logger.info(cmd) - subprocess.check_call(cmd, shell=True) + subprocess.check_call(cmd, env=DEFAULT_ENV, shell=True) diff --git a/pykeg/core/management/commands/backup.py b/pykeg/core/management/commands/backup.py index 66df86a9..66d373a2 100644 --- a/pykeg/core/management/commands/backup.py +++ b/pykeg/core/management/commands/backup.py @@ -1,6 +1,6 @@ import os -from django.core.files.storage import get_storage_class +from django.core.files.storage import default_storage from django.core.management.base import BaseCommand from pykeg.backup import backup @@ -21,7 +21,7 @@ def add_arguments(self, parser): def handle(self, **options): location = backup.backup(include_media=not options.get("no_media")) - storage = get_storage_class()() + storage = default_storage path = location if hasattr(storage, "location"): From 1786084ccb1fb90889c82f587560b35c15bb51cd Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 16:28:59 +0000 Subject: [PATCH 26/30] ci: test upgrades from a frozen v1.3 backup - restores testdata/legacy-backups/kegbot-v1.3-{mysql,postgres}.zip, runs kegbot upgrade, and verifies the result against the demo fixture - self-skips until the frozen zips are committed - frozen zips mean future migrations are always tested against a database as it existed at v1.3 --- .github/workflows/legacy-upgrade.yml | 107 +++++++++++++++++++++++++++ bin/check-upgraded-site.py | 60 +++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 .github/workflows/legacy-upgrade.yml create mode 100755 bin/check-upgraded-site.py diff --git a/.github/workflows/legacy-upgrade.yml b/.github/workflows/legacy-upgrade.yml new file mode 100644 index 00000000..de270bf0 --- /dev/null +++ b/.github/workflows/legacy-upgrade.yml @@ -0,0 +1,107 @@ +name: Legacy Upgrade Test + +# Restores the frozen v1.3 backup zips committed under +# testdata/legacy-backups/, runs `kegbot upgrade`, and verifies the +# result. Because the committed zips are frozen, any future migration is +# tested against a database as it existed at v1.3. +# +# The zips are built locally with bin/generate-legacy-backups.sh (docker +# compose). The steps below self-skip while no zips are committed. + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + REDIS_URL: redis://localhost:6379/0 + KEGBOT_SECRET_KEY: changeme + +jobs: + upgrade: + name: 'Upgrade from v1.3 (${{ matrix.database }})' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + database: [mysql, postgres] + include: + - database: mysql + database_url: mysql://root:cipass@127.0.0.1/kegbot_ci + - database: postgres + database_url: postgres://postgres:cipass@127.0.0.1/kegbot_ci + + services: + mysql: + image: ${{ matrix.database == 'mysql' && 'mysql:8' || '' }} + env: + MYSQL_ROOT_PASSWORD: cipass + MYSQL_DATABASE: kegbot_ci + ports: + - 3306:3306 + options: --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 10 + + postgres: + image: ${{ matrix.database == 'postgres' && 'postgres:16' || '' }} + env: + POSTGRES_PASSWORD: cipass + POSTGRES_DB: kegbot_ci + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 10 + + redis: + image: redis + ports: + - 6379:6379 + + env: + DATABASE_URL: ${{ matrix.database_url }} + KEGBOT_DATA_DIR: ${{ github.workspace }}/kegbot-data + + steps: + - uses: actions/checkout@v6 + + - name: Check for frozen backup + id: frozen + run: | + if [ -f "testdata/legacy-backups/kegbot-v1.3-${{ matrix.database }}.zip" ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "No frozen backup committed yet; skipping." + fi + + - name: Install uv + if: steps.frozen.outputs.present == 'true' + uses: astral-sh/setup-uv@v7 + + - name: Set up Python 3.14 + if: steps.frozen.outputs.present == 'true' + uses: actions/setup-python@v6 + with: + python-version: '3.14' + + - name: Install system libraries + if: steps.frozen.outputs.present == 'true' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + default-libmysqlclient-dev libpq-dev pkg-config + + - name: Install dependencies + if: steps.frozen.outputs.present == 'true' + run: uv sync --all-groups + + - name: Restore v1.3 backup + if: steps.frozen.outputs.present == 'true' + run: uv run bin/kegbot restore testdata/legacy-backups/kegbot-v1.3-${{ matrix.database }}.zip + + - name: Run upgrade + if: steps.frozen.outputs.present == 'true' + run: uv run bin/kegbot upgrade + + - name: Verify upgraded site + if: steps.frozen.outputs.present == 'true' + run: uv run python bin/check-upgraded-site.py diff --git a/bin/check-upgraded-site.py b/bin/check-upgraded-site.py new file mode 100755 index 00000000..e8a8b353 --- /dev/null +++ b/bin/check-upgraded-site.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +"""Verifies a site restored from a v1.3 backup and upgraded to this version. + +Run after `kegbot restore <legacy zip>` + `kegbot upgrade`. Compares the +database against testdata/demo-site.json (the data the legacy backups were +built from) and smoke-tests key pages. Exits nonzero on any failure. +""" + +import json +import os +import sys +from collections import Counter + +import django + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pykeg.settings") +django.setup() + +from django.apps import apps # noqa: E402 +from django.test import Client # noqa: E402 + +from pykeg.core import models # noqa: E402 +from pykeg.core.util import get_version # noqa: E402 + +failures = [] + + +def check(label, expected, actual): + if expected == actual: + print(f"ok: {label}: {actual}") + else: + print(f"FAIL: {label}: expected {expected}, got {actual}") + failures.append(label) + + +with open("testdata/demo-site.json") as f: + fixture_counts = Counter(row["model"] for row in json.load(f)) + +for label, expected in sorted(fixture_counts.items()): + check(f"count {label}", expected, apps.get_model(label).objects.count()) + +site = models.KegbotSite.get() +check("server_version", get_version(), site.server_version) +check("site is_setup", True, site.is_setup) +check("kegs on tap", 2, models.Keg.objects.filter(status=models.Keg.STATUS_ON_TAP).count()) + +client = Client() +for url in ["/", "/kegs/", "/stats/", "/sessions/", "/accounts/login/"]: + response = client.get(url, follow=True) + check(f"GET {url}", 200, response.status_code) + +api_key = models.ApiKey.objects.first() +if api_key: + response = client.get(f"/api/v1/status/?api_key={api_key.key}") + check("GET /api/v1/status (restored api key)", 200, response.status_code) + +if failures: + print(f"\n{len(failures)} check(s) failed") + sys.exit(1) +print("\nAll checks passed.") From 56e6fcc0b61faf25427f083c2629f72d275e4f9a Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 16:28:59 +0000 Subject: [PATCH 27/30] docker: install mysql and postgres client tools - kegbot backup/restore/erase shell out to mysqldump/mysql/pg_dump/psql, which were missing from the image --- Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 43b7b48d..23c57f57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,13 +9,17 @@ ENV SHELL=/bin/sh \ KEGBOT_ENV=debug # Build/runtime libraries: MySQL + Postgres client headers (mysqlclient, -# psycopg2 build from source) and the image libraries Pillow needs. +# psycopg2 build from source), client tools (`kegbot backup`/`restore` +# shell out to mysqldump/mysql/pg_dump/psql), and the image libraries +# Pillow needs. RUN apt-get -qq update \ && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ build-essential \ pkg-config \ default-libmysqlclient-dev \ + default-mysql-client \ libpq-dev \ + postgresql-client \ libjpeg-dev \ libfreetype6-dev \ liblcms2-dev \ From 8e35bc7c054923d1b1c694a2343a4ee1e675d032 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 16:28:59 +0000 Subject: [PATCH 28/30] testdata: generate legacy backup fixtures with docker compose - bin/generate-legacy-backups.sh builds v1.3-site backup zips from testdata/demo-site.json using the server image against pinned mysql:8/postgres:16, then they are committed as frozen fixtures --- bin/build-legacy-backup.sh | 28 ++++++++++++ bin/generate-legacy-backups.sh | 28 ++++++++++++ testdata/legacy-backup-compose.yml | 72 ++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100755 bin/build-legacy-backup.sh create mode 100755 bin/generate-legacy-backups.sh create mode 100644 testdata/legacy-backup-compose.yml diff --git a/bin/build-legacy-backup.sh b/bin/build-legacy-backup.sh new file mode 100755 index 00000000..641fab77 --- /dev/null +++ b/bin/build-legacy-backup.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# Builds a "v1.3 site" backup zip for upgrade-path testing. +# +# Usage: build-legacy-backup.sh <output.zip> +# +# Requires a fresh, empty database at DATABASE_URL (mysql or postgres), +# redis at REDIS_URL, and a writable KEGBOT_DATA_DIR. The database is +# populated from testdata/demo-site.json, which records a site at +# server_version 1.3.0, then dumped with `kegbot backup` (format 2). +# +# Normally run inside docker compose via bin/generate-legacy-backups.sh, +# which builds the frozen zips checked in under testdata/legacy-backups/. + +set -eu + +OUT="${1:?usage: $0 <output.zip>}" + +bin/kegbot migrate --noinput -v 0 +bin/kegbot loaddata testdata/demo-site.json + +ZIP_PATH=$(bin/kegbot backup | sed -n 's/^Path: //p') +if [ -z "$ZIP_PATH" ] || [ ! -f "$ZIP_PATH" ]; then + echo "error: backup zip not found (got: '$ZIP_PATH')" >&2 + exit 1 +fi + +cp "$ZIP_PATH" "$OUT" +echo "Wrote $OUT" diff --git a/bin/generate-legacy-backups.sh b/bin/generate-legacy-backups.sh new file mode 100755 index 00000000..b2de4be6 --- /dev/null +++ b/bin/generate-legacy-backups.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# Generates the frozen v1.3 backup fixtures with docker compose. +# +# Usage: bin/generate-legacy-backups.sh +# +# Writes testdata/legacy-backups/kegbot-v1.3-{mysql,postgres}.zip, built +# from testdata/demo-site.json against pinned mysql/postgres images. +# Commit the zips to update the fixtures used by the Legacy Upgrade Test +# workflow. +# +# Regenerate deliberately, not routinely: the fixtures are frozen so that +# future migrations are tested against a database as it existed at v1.3. + +set -eu + +cd "$(dirname "$0")/.." +mkdir -p testdata/legacy-backups + +COMPOSE="docker compose -f testdata/legacy-backup-compose.yml" +trap '$COMPOSE down -v --remove-orphans' EXIT + +$COMPOSE build generate-mysql +$COMPOSE run --rm generate-mysql +$COMPOSE run --rm generate-postgres + +echo "" +echo "Fixtures written:" +ls -l testdata/legacy-backups/*.zip diff --git a/testdata/legacy-backup-compose.yml b/testdata/legacy-backup-compose.yml new file mode 100644 index 00000000..dc4081dc --- /dev/null +++ b/testdata/legacy-backup-compose.yml @@ -0,0 +1,72 @@ +# Generates the frozen v1.3 backup fixtures under testdata/legacy-backups/. +# Run via `bin/generate-legacy-backups.sh` from the repo root. +# +# The generate services run the real server image (built from the local +# checkout) against pinned database images, so the fixtures are +# reproducible regardless of what is installed on the host. + +services: + mysql: + image: mysql:8 + environment: + MYSQL_ROOT_PASSWORD: changeme + MYSQL_DATABASE: kegbot + healthcheck: + # Ping over TCP: during init the image runs a socket-only temporary + # server that would pass a plain socket ping too early. + test: ["CMD", "mysqladmin", "ping", "-h127.0.0.1", "--silent"] + interval: 5s + timeout: 5s + retries: 30 + + postgres: + image: postgres:16 + environment: + POSTGRES_PASSWORD: changeme + POSTGRES_DB: kegbot + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 5s + timeout: 5s + retries: 30 + + redis: + image: redis + + generate-mysql: + build: + context: .. + entrypoint: [] + working_dir: /app + command: ["bin/build-legacy-backup.sh", "testdata/legacy-backups/kegbot-v1.3-mysql.zip"] + environment: + DATABASE_URL: mysql://root:changeme@mysql/kegbot + REDIS_URL: redis://redis:6379/0 + KEGBOT_SECRET_KEY: changeme + volumes: + - ../bin:/app/bin:ro + - ../testdata:/app/testdata + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_started + + generate-postgres: + build: + context: .. + entrypoint: [] + working_dir: /app + command: ["bin/build-legacy-backup.sh", "testdata/legacy-backups/kegbot-v1.3-postgres.zip"] + environment: + DATABASE_URL: postgres://postgres:changeme@postgres/kegbot + REDIS_URL: redis://redis:6379/0 + KEGBOT_SECRET_KEY: changeme + volumes: + - ../bin:/app/bin:ro + - ../testdata:/app/testdata + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_started From 8ef88ec345c72febc38922c735b7376c08663120 Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 16:49:46 +0000 Subject: [PATCH 29/30] backup: skip mysql server cert verification in cli tools - mariadb 11.4+ clients (debian's default-mysql-client) verify server certs by default and reject the self-signed certs mysql servers auto-generate; keep TLS but skip verification, matching the Django connection - the loose- prefix makes oracle mysql clients warn instead of exit on the unknown option --- pykeg/backup/mysql.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/pykeg/backup/mysql.py b/pykeg/backup/mysql.py index 75482f7e..cf8b79b9 100644 --- a/pykeg/backup/mysql.py +++ b/pykeg/backup/mysql.py @@ -29,6 +29,13 @@ if PARAMS.get("port"): DEFAULT_ARGS.append("--port={}".format(PARAMS["port"])) +# MariaDB 11.4+ clients verify server certificates by default, which fails +# against the self-signed certs MySQL servers auto-generate. Keep TLS but +# skip verification, matching how the Django connection behaves. The +# "loose-" prefix makes clients without this option (Oracle mysql) warn +# instead of exit. +DEFAULT_ARGS.append("--loose-ssl-verify-server-cert=0") + def engine_name(): return "mysql" @@ -46,16 +53,7 @@ def is_installed(): def dump(output_fd): - args = ["mysqldump", "--skip-dump-date", "--single-transaction"] - if PARAMS.get("user"): - args.append("--user={}".format(PARAMS["user"])) - if PARAMS.get("password"): - args.append("--password={}".format(PARAMS["password"])) - if PARAMS.get("host"): - args.append("--host={}".format(PARAMS["host"])) - if PARAMS.get("port"): - args.append("--port={}".format(PARAMS["port"])) - + args = ["mysqldump", "--skip-dump-date", "--single-transaction"] + DEFAULT_ARGS args.append(PARAMS["db"]) cmd = " ".join(args) logger.info(cmd) @@ -72,17 +70,7 @@ def restore(input_fd): def erase(): - args = ["mysql"] - if PARAMS.get("user"): - args.append("--user={}".format(PARAMS["user"])) - if PARAMS.get("password"): - args.append("--password={}".format(PARAMS["password"])) - if PARAMS.get("host"): - args.append("--host={}".format(PARAMS["host"])) - if PARAMS.get("port"): - args.append("--port={}".format(PARAMS["port"])) - - args += [PARAMS["db"]] + args = ["mysql"] + DEFAULT_ARGS + [PARAMS["db"]] # Build the sql command. tables = [str(model._meta.db_table) for model in apps.get_models()] From 29c0e8ad66f355c471b27aac7593ae06f6aed2aa Mon Sep 17 00:00:00 2001 From: mike wakerly <opensource@hoho.com> Date: Mon, 3 Aug 2026 17:12:22 +0000 Subject: [PATCH 30/30] testdata: add frozen v1.3 backup fixtures - generated with bin/generate-legacy-backups.sh (demo-site data, stored server_version 1.3.0) - activates the 'Upgrade from v1.3' CI jobs --- testdata/legacy-backups/kegbot-v1.3-mysql.zip | Bin 0 -> 363519 bytes .../legacy-backups/kegbot-v1.3-postgres.zip | Bin 0 -> 359444 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 testdata/legacy-backups/kegbot-v1.3-mysql.zip create mode 100644 testdata/legacy-backups/kegbot-v1.3-postgres.zip diff --git a/testdata/legacy-backups/kegbot-v1.3-mysql.zip b/testdata/legacy-backups/kegbot-v1.3-mysql.zip new file mode 100644 index 0000000000000000000000000000000000000000..11b15fdb42652305b2a73a1098965702777d5391 GIT binary patch literal 363519 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 CrF1O- literal 0 HcmV?d00001 diff --git a/testdata/legacy-backups/kegbot-v1.3-postgres.zip b/testdata/legacy-backups/kegbot-v1.3-postgres.zip new file mode 100644 index 0000000000000000000000000000000000000000..471ff2a7ffc3bd1162752f3340989693f4ac74e2 GIT binary patch literal 359444 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 literal 0 HcmV?d00001