From 809febc35326a7c637f4229e953bcc16550254a8 Mon Sep 17 00:00:00 2001 From: imperosol Date: Sun, 23 Feb 2025 12:14:32 +0100 Subject: [PATCH 01/56] add club and counter filters on product list page --- core/static/core/style.scss | 2 +- counter/schemas.py | 2 + .../bundled/counter/product-list-index.ts | 43 ++++++++++++++----- counter/templates/counter/product_list.jinja | 40 ++++++++++++----- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 06208579..d97629ca 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -447,7 +447,7 @@ body { flex-wrap: wrap; $col-gap: 1rem; - $row-gap: 0.5rem; + $row-gap: $col-gap / 3; &.gap { column-gap: $col-gap; diff --git a/counter/schemas.py b/counter/schemas.py index adc8094b..978422a5 100644 --- a/counter/schemas.py +++ b/counter/schemas.py @@ -98,3 +98,5 @@ class ProductFilterSchema(FilterSchema): is_archived: bool | None = Field(None, q="archived") buying_groups: set[int] | None = Field(None, q="buying_groups__in") product_type: set[int] | None = Field(None, q="product_type__in") + club: set[int] | None = Field(None, q="club__in") + counter: set[int] | None = Field(None, q="counters__in") diff --git a/counter/static/bundled/counter/product-list-index.ts b/counter/static/bundled/counter/product-list-index.ts index 70403692..af45a1e3 100644 --- a/counter/static/bundled/counter/product-list-index.ts +++ b/counter/static/bundled/counter/product-list-index.ts @@ -60,6 +60,8 @@ document.addEventListener("alpine:init", () => { productStatus: "" as "active" | "archived" | "both", search: "", productTypes: [] as string[], + clubs: [] as string[], + counters: [] as string[], pageSize: defaultPageSize, page: defaultPage, @@ -67,13 +69,27 @@ document.addEventListener("alpine:init", () => { const url = getCurrentUrlParams(); this.search = url.get("search") || ""; this.productStatus = url.get("productStatus") ?? "active"; - const widget = this.$refs.productTypesInput.widget as TomSelect; - widget.on("change", (items: string[]) => { + const productTypesWidget = this.$refs.productTypesInput.widget as TomSelect; + productTypesWidget.on("change", (items: string[]) => { this.productTypes = [...items]; }); + const clubsWidget = this.$refs.clubsInput.widget as TomSelect; + clubsWidget.on("change", (items: string[]) => { + this.clubs = [...items]; + }); + const countersWidget = this.$refs.countersInput.widget as TomSelect; + countersWidget.on("change", (items: string[]) => { + this.counters = [...items]; + }); await this.load(); - const searchParams = ["search", "productStatus", "productTypes"]; + const searchParams = [ + "search", + "productStatus", + "productTypes", + "clubs", + "counters", + ]; for (const param of searchParams) { this.$watch(param, () => { this.page = defaultPage; @@ -109,6 +125,8 @@ document.addEventListener("alpine:init", () => { is_archived: isArchived, // biome-ignore lint/style/useNamingConvention: api is in snake_case product_type: [...this.productTypes], + club: [...this.clubs], + counter: [...this.counters], }, }; }, @@ -121,14 +139,17 @@ document.addEventListener("alpine:init", () => { const options = this.getQueryParams(); const resp = await productSearchProductsDetailed(options); this.nbPages = Math.ceil(resp.data.count / defaultPageSize); - this.products = resp.data.results.reduce((acc, curr) => { - const key = curr.product_type?.name ?? gettext("Uncategorized"); - if (!(key in acc)) { - acc[key] = []; - } - acc[key].push(curr); - return acc; - }, {}); + this.products = resp.data.results.reduce( + (acc: GroupedProducts, curr: ProductSchema) => { + const key = curr.product_type?.name ?? gettext("Uncategorized"); + if (!(key in acc)) { + acc[key] = []; + } + acc[key].push(curr); + return acc; + }, + {}, + ); this.loading = false; }, diff --git a/counter/templates/counter/product_list.jinja b/counter/templates/counter/product_list.jinja index 0ee8dffa..9644e88f 100644 --- a/counter/templates/counter/product_list.jinja +++ b/counter/templates/counter/product_list.jinja @@ -7,6 +7,7 @@ {% block additional_js %} + {% endblock %} @@ -22,7 +23,6 @@

{% trans %}Filter products{% endtrans %}

-
-
- - - -
+
+
+ + +
+
+ + +
+
+ + +
+

{% trans %}Product list{% endtrans %}

From 602c57c001e5c91216aa0302ffe2a1103369dbd2 Mon Sep 17 00:00:00 2001 From: NaNoMelo <56289688+NaNoMelo@users.noreply.github.com> Date: Fri, 28 Feb 2025 19:20:19 +0100 Subject: [PATCH 02/56] fix com poster --- com/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com/models.py b/com/models.py index 2b3a76c4..c7e66515 100644 --- a/com/models.py +++ b/com/models.py @@ -337,7 +337,7 @@ class Screen(models.Model): def active_posters(self): now = timezone.now() - return self.posters.filter(d=True, date_begin__lte=now).filter( + return self.posters.filter(is_moderated=True, date_begin__lte=now).filter( Q(date_end__isnull=True) | Q(date_end__gte=now) ) From fe417b0c297ab5d933cf5e40d2c0c22322a3bc77 Mon Sep 17 00:00:00 2001 From: Sli Date: Mon, 3 Mar 2025 13:33:58 +0100 Subject: [PATCH 03/56] Enable csrf tokens on API routes * Upgrade openapi-ts * Migrate openapi-ts settings to new version * Add csrf token to headers of all API calls * Force csrf token authentication on API routes --- openapi-csrf.ts | 9 +++++++++ openapi-ts.config.ts | 15 +++++++++++++-- package-lock.json | 36 +++++++++++++++++++++++------------- package.json | 5 +++-- sith/urls.py | 2 +- 5 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 openapi-csrf.ts diff --git a/openapi-csrf.ts b/openapi-csrf.ts new file mode 100644 index 00000000..5d1cf84b --- /dev/null +++ b/openapi-csrf.ts @@ -0,0 +1,9 @@ +import Cookies from "js-cookie"; +import type { CreateClientConfig } from "#openapi"; + +export const createClientConfig: CreateClientConfig = (config) => ({ + ...config, + headers: { + "X-CSRFToken": Cookies.get("csrftoken"), + }, +}); diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index d583ffee..32403271 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -4,7 +4,18 @@ import { defineConfig } from "@hey-api/openapi-ts"; // biome-ignore lint/style/noDefaultExport: needed for openapi-ts export default defineConfig({ - client: "@hey-api/client-fetch", input: resolve(__dirname, "./staticfiles/generated/openapi/schema.json"), - output: resolve(__dirname, "./staticfiles/generated/openapi"), + output: { + lint: "biome", + format: "biome", + path: resolve(__dirname, "./staticfiles/generated/openapi"), + }, + plugins: [ + { + name: "@hey-api/client-fetch", + baseUrl: false, + runtimeConfigPath: "./openapi-csrf.ts", + exportFromIndex: true, + }, + ], }); diff --git a/package-lock.json b/package-lock.json index 012e48e7..9126ac34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/icalendar": "^6.1.15", "@fullcalendar/list": "^6.1.15", - "@hey-api/client-fetch": "^0.6.0", + "@hey-api/client-fetch": "^0.8.2", "@sentry/browser": "^8.34.0", "@zip.js/zip.js": "^2.7.52", "3d-force-graph": "^1.73.4", @@ -31,6 +31,7 @@ "htmx.org": "^2.0.3", "jquery": "^3.7.1", "jquery-ui": "^1.14.0", + "js-cookie": "^3.0.5", "native-file-system-adapter": "^3.0.1", "three": "^0.172.0", "three-spritetext": "^1.9.0", @@ -40,7 +41,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.4", "@biomejs/biome": "1.9.4", - "@hey-api/openapi-ts": "^0.61.3", + "@hey-api/openapi-ts": "^0.64.0", "@rollup/plugin-inject": "^5.0.5", "@types/alpinejs": "^3.13.10", "@types/jquery": "^3.5.31", @@ -2207,18 +2208,18 @@ } }, "node_modules/@hey-api/client-fetch": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.6.0.tgz", - "integrity": "sha512-FlhFsVeH8RxJe/nq8xUzxNbiOpe+GadxlD2pfvDyOyLdCTU4o/LRv46ZVWstaW7DgF4nxhI328chy3+AulwVXw==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.8.2.tgz", + "integrity": "sha512-61T4UGfAzY5345vMxWDX8qnSTNRJcOpWuZyvNu3vNebCTLPwMQAM85mhEuBoACdWeRtLhNoUjU0UR5liRyD1bA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/hey-api" } }, "node_modules/@hey-api/json-schema-ref-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.1.tgz", - "integrity": "sha512-dBt0A7op9kf4BcK++x6HBYDmvCvnJUZEGe5QytghPFHnMXPyKwDKomwL/v5e9ERk6E0e1GzL/e/y6pWUso9zrQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.2.tgz", + "integrity": "sha512-F6LSkttZcT/XiX3ydeDqTY3uRN3BLJMwyMTk4kg/ichZlKUp3+3Odv0WokSmXGSoZGTW/N66FROMYAm5NPdJlA==", "dev": true, "license": "MIT", "dependencies": { @@ -2234,13 +2235,13 @@ } }, "node_modules/@hey-api/openapi-ts": { - "version": "0.61.3", - "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.61.3.tgz", - "integrity": "sha512-Ls9MBRa5+vg7UHw6fIcfdgcCyZ9vKtRw63nWxwF9zjJIPlzVOZO6xKuzGmDc6o0Pb6XCdTz6lPV5hcV0R4b/ag==", + "version": "0.64.8", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.64.8.tgz", + "integrity": "sha512-ytPt/k+ecK7zcpxVocPWzD1bKn98a+9WDK8eJITvbOEkvYsWlozAPO63tQg+65Qpl2pr37025fEo8YcX+DPhBQ==", "dev": true, "license": "MIT", "dependencies": { - "@hey-api/json-schema-ref-parser": "1.0.1", + "@hey-api/json-schema-ref-parser": "1.0.2", "c12": "2.0.1", "commander": "13.0.0", "handlebars": "4.7.8" @@ -2249,7 +2250,7 @@ "openapi-ts": "bin/index.cjs" }, "engines": { - "node": "^18.20.5 || ^20.11.1 || >=22.11.0" + "node": "^18.18.0 || ^20.9.0 || >=22.10.0" }, "funding": { "url": "https://github.com/sponsors/hey-api" @@ -4295,6 +4296,15 @@ "jquery": ">=1.12.0 <5.0.0" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 93494819..f46df7db 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.4", "@biomejs/biome": "1.9.4", - "@hey-api/openapi-ts": "^0.61.3", + "@hey-api/openapi-ts": "^0.64.0", "@rollup/plugin-inject": "^5.0.5", "@types/alpinejs": "^3.13.10", "@types/jquery": "^3.5.31", @@ -42,7 +42,7 @@ "@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/icalendar": "^6.1.15", "@fullcalendar/list": "^6.1.15", - "@hey-api/client-fetch": "^0.6.0", + "@hey-api/client-fetch": "^0.8.2", "@sentry/browser": "^8.34.0", "@zip.js/zip.js": "^2.7.52", "3d-force-graph": "^1.73.4", @@ -57,6 +57,7 @@ "htmx.org": "^2.0.3", "jquery": "^3.7.1", "jquery-ui": "^1.14.0", + "js-cookie": "^3.0.5", "native-file-system-adapter": "^3.0.1", "three": "^0.172.0", "three-spritetext": "^1.9.0", diff --git a/sith/urls.py b/sith/urls.py index f6f7c8bb..0dfa1310 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -27,7 +27,7 @@ handler403 = "core.views.forbidden" handler404 = "core.views.not_found" handler500 = "core.views.internal_servor_error" -api = NinjaExtraAPI(version="0.2.0", urls_namespace="api") +api = NinjaExtraAPI(version="0.2.0", urls_namespace="api", csrf=True) api.auto_discover_controllers() urlpatterns = [ From 8528820d89fde995bc8d59481fef6302f441f70c Mon Sep 17 00:00:00 2001 From: Sli Date: Sat, 15 Feb 2025 12:15:08 +0100 Subject: [PATCH 04/56] Run bundler through honcho --- Procfile | 1 + pyproject.toml | 1 + staticfiles/management/commands/runserver.py | 10 +++++++--- staticfiles/processors.py | 6 ------ uv.lock | 18 ++++++++++++++++-- 5 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..857b2f2d --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +bundler: npm run serve \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a4d16abc..cf09d61f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ dependencies = [ "redis[hiredis]<6.0.0,>=5.2.0", "environs[django]<15.0.0,>=14.1.0", "requests>=2.32.3", + "honcho>=2.0.0", ] [project.urls] diff --git a/staticfiles/management/commands/runserver.py b/staticfiles/management/commands/runserver.py index f093f3bd..8614d8b9 100644 --- a/staticfiles/management/commands/runserver.py +++ b/staticfiles/management/commands/runserver.py @@ -1,4 +1,7 @@ +import logging import os +import subprocess +import sys from django.conf import settings from django.contrib.staticfiles.management.commands.runserver import ( @@ -6,7 +9,7 @@ from django.contrib.staticfiles.management.commands.runserver import ( ) from django.utils.autoreload import DJANGO_AUTORELOAD_ENV -from staticfiles.processors import JSBundler, OpenApi +from staticfiles.processors import OpenApi class Command(Runserver): @@ -15,10 +18,11 @@ class Command(Runserver): def run(self, **options): # OpenApi generation needs to be before the bundler OpenApi.compile() - # Only run the bundling server when debug is enabled + # Run all other web processes but only if debug mode is enabled # Also protects from re-launching the server if django reloads it if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and settings.DEBUG: - with JSBundler.runserver(): + logging.getLogger("django").info("Running complementary processes") + with subprocess.Popen([sys.executable, "-m", "honcho", "start"]): super().run(**options) return super().run(**options) diff --git a/staticfiles/processors.py b/staticfiles/processors.py index 3a0df243..bacac363 100644 --- a/staticfiles/processors.py +++ b/staticfiles/processors.py @@ -99,12 +99,6 @@ class JSBundler: if process.returncode: raise RuntimeError(f"Bundler failed with returncode {process.returncode}") - @staticmethod - def runserver() -> subprocess.Popen: - """Bundle js files automatically in background when called in debug mode.""" - logging.getLogger("django").info("Running javascript bundling server") - return subprocess.Popen(["npm", "run", "serve"]) - @staticmethod def get_manifest() -> JSBundlerManifest: return JSBundlerManifest(BUNDLED_ROOT / ".vite" / "manifest.json") diff --git a/uv.lock b/uv.lock index c76028d9..69a94a95 100644 --- a/uv.lock +++ b/uv.lock @@ -155,7 +155,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -595,6 +595,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/04/eaa88433249ddfc282018d3da4198d0b0018e48768e137bfad304aacb1ec/hiredis-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9020fd7e58f489fda6a928c31355add0e665fd6b87b21954e675cf9943eafa32", size = 22004 }, ] +[[package]] +name = "honcho" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/c8/d860888358bf5c8a6e7d78d1b508b59b0e255afd5655f243b8f65166dafd/honcho-2.0.0.tar.gz", hash = "sha256:af3815c03c634bf67d50f114253ea9fef72ecff26e4fd06b29234789ac5b8b2e", size = 45618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/1c/25631fc359955569e63f5446dbb7022c320edf9846cbe892ee5113433a7e/honcho-2.0.0-py3-none-any.whl", hash = "sha256:56dcd04fc72d362a4befb9303b1a1a812cba5da283526fbc6509be122918ddf3", size = 22093 }, +] + [[package]] name = "ical" version = "8.3.1" @@ -807,7 +819,7 @@ version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, { name = "jinja2" }, { name = "markdown" }, @@ -1501,6 +1513,7 @@ dependencies = [ { name = "django-phonenumber-field" }, { name = "django-simple-captcha" }, { name = "environs", extra = ["django"] }, + { name = "honcho" }, { name = "ical" }, { name = "jinja2" }, { name = "libsass" }, @@ -1561,6 +1574,7 @@ requires-dist = [ { name = "django-phonenumber-field", specifier = ">=8.0.0,<9.0.0" }, { name = "django-simple-captcha", specifier = ">=0.6.0,<1.0.0" }, { name = "environs", extras = ["django"], specifier = ">=14.1.0,<15.0.0" }, + { name = "honcho", specifier = ">=2.0.0" }, { name = "ical", specifier = ">=8.3.0,<9.0.0" }, { name = "jinja2", specifier = ">=3.1.4,<4.0.0" }, { name = "libsass", specifier = ">=0.23.0,<1.0.0" }, From 6841d96455f993e2848422f7eb86b7437da6b1b5 Mon Sep 17 00:00:00 2001 From: Sli Date: Sat, 15 Feb 2025 17:58:07 +0100 Subject: [PATCH 05/56] Enable honcho on non debug --- Procfile | 2 +- docs/tutorial/structure.md | 6 ++++-- staticfiles/management/commands/runserver.py | 10 ++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Procfile b/Procfile index 857b2f2d..f3ce1a69 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -bundler: npm run serve \ No newline at end of file +bundler: npm run $BUNDLER_MODE \ No newline at end of file diff --git a/docs/tutorial/structure.md b/docs/tutorial/structure.md index aff331d2..4ba0077e 100644 --- a/docs/tutorial/structure.md +++ b/docs/tutorial/structure.md @@ -80,6 +80,7 @@ sith/ ├── pyproject.toml (30) ├── .venv/ (31) ├── .python-version (32) +├── Procfile (33) └── README.md ``` @@ -136,7 +137,8 @@ sith/ de certaines d'entre elles. 31. Dossier d'environnement virtuel généré par uv 32. Fichier qui contrôle quelle version de python utiliser pour le projet - +33. Fichier qui contrôle les services additionnels à lancer + avec le serveur de développement ## L'application principale @@ -220,4 +222,4 @@ comme suit : L'organisation peut éventuellement être un peu différente pour certaines applications, mais le principe -général est le même. \ No newline at end of file +général est le même. diff --git a/staticfiles/management/commands/runserver.py b/staticfiles/management/commands/runserver.py index 8614d8b9..5fc80a64 100644 --- a/staticfiles/management/commands/runserver.py +++ b/staticfiles/management/commands/runserver.py @@ -20,9 +20,15 @@ class Command(Runserver): OpenApi.compile() # Run all other web processes but only if debug mode is enabled # Also protects from re-launching the server if django reloads it - if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and settings.DEBUG: + if os.environ.get(DJANGO_AUTORELOAD_ENV) is None: logging.getLogger("django").info("Running complementary processes") - with subprocess.Popen([sys.executable, "-m", "honcho", "start"]): + with subprocess.Popen( + [sys.executable, "-m", "honcho", "start"], + env={ + **os.environ, + **{"BUNDLER_MODE": "serve" if settings.DEBUG else "compile"}, + }, + ): super().run(**options) return super().run(**options) From ba6e2a6402c8bd53873a30b4d6a3dd51297f3061 Mon Sep 17 00:00:00 2001 From: Sli Date: Mon, 17 Feb 2025 15:58:53 +0100 Subject: [PATCH 06/56] Integrate automatic redis startup with the project --- .env.example | 8 +++++- .gitignore | 6 +++++ Procfile | 1 - Procfile.dev | 2 ++ Procfile.pytest | 1 + manage.py | 14 ++++++++++- processes/composer.py | 26 ++++++++++++++++++++ pyproject.toml | 4 +++ sith/environ.py | 4 +++ sith/pytest.py | 23 +++++++++++++++++ sith/settings.py | 5 +--- staticfiles/management/commands/runserver.py | 21 ---------------- uv.lock | 17 +++++++++++++ 13 files changed, 104 insertions(+), 28 deletions(-) delete mode 100644 Procfile create mode 100644 Procfile.dev create mode 100644 Procfile.pytest create mode 100644 processes/composer.py create mode 100644 sith/environ.py create mode 100644 sith/pytest.py diff --git a/.env.example b/.env.example index 5c4c0d97..da4d2ac8 100644 --- a/.env.example +++ b/.env.example @@ -8,4 +8,10 @@ SECRET_KEY=(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2 DATABASE_URL=sqlite:///db.sqlite3 #DATABASE_URL=postgres://user:password@127.0.0.1:5432/sith -CACHE_URL=redis://127.0.0.1:6379/0 +REDIS_PORT=7963 +CACHE_URL=redis://127.0.0.1:${REDIS_PORT}/0 + +# Used to select which other services to run alongside +# runserver and pytest +PROCFILE_RUNSERVER=Procfile.dev +PROCFILE_PYTEST=Procfile.pytest diff --git a/.gitignore b/.gitignore index 19b65265..eb78b41d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ node_modules/ # compiled documentation site/ .env + +### Redis ### + +# Ignore redis binary dump (dump.rdb) files + +*.rdb diff --git a/Procfile b/Procfile deleted file mode 100644 index f3ce1a69..00000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -bundler: npm run $BUNDLER_MODE \ No newline at end of file diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 00000000..31652ef9 --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +bundler: npm run serve +redis: redis-server --port $REDIS_PORT \ No newline at end of file diff --git a/Procfile.pytest b/Procfile.pytest new file mode 100644 index 00000000..4f9c4808 --- /dev/null +++ b/Procfile.pytest @@ -0,0 +1 @@ +redis: redis-server --port $REDIS_PORT diff --git a/manage.py b/manage.py index 56271706..71939ec3 100755 --- a/manage.py +++ b/manage.py @@ -13,13 +13,25 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # - import os import sys +from django.utils.autoreload import DJANGO_AUTORELOAD_ENV + +from processes.composer import start_composer, stop_composer +from sith.environ import env + if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sith.settings") from django.core.management import execute_from_command_line + if ( + os.environ.get(DJANGO_AUTORELOAD_ENV) is None + and (procfile := env.str("PROCFILE_RUNSERVER", None)) is not None + ): + start_composer(procfile) + execute_from_command_line(sys.argv) + + stop_composer() diff --git a/processes/composer.py b/processes/composer.py new file mode 100644 index 00000000..8355e205 --- /dev/null +++ b/processes/composer.py @@ -0,0 +1,26 @@ +import os +import signal +import subprocess +import sys + +import psutil + +COMPOSER_PID = "COMPOSER_PID" + + +def start_composer(procfile: str): + """Starts the composer and stores the PID as an environment variable + This allows for running smoothly with the django reloader + """ + process = subprocess.Popen( + [sys.executable, "-m", "honcho", "-f", procfile, "start"], + ) + os.environ[COMPOSER_PID] = str(process.pid) + + +def stop_composer(): + """Stops the composer if it was started before""" + if (pid := os.environ.get(COMPOSER_PID, None)) is not None: + process = psutil.Process(int(pid)) + process.send_signal(signal.SIGTERM) + process.wait() diff --git a/pyproject.toml b/pyproject.toml index cf09d61f..d5d3c1f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ dependencies = [ "environs[django]<15.0.0,>=14.1.0", "requests>=2.32.3", "honcho>=2.0.0", + "psutil>=7.0.0", ] [project.urls] @@ -125,6 +126,9 @@ ignore = [ [tool.ruff.lint.pydocstyle] convention = "google" +[project.entry-points.pytest11] +sith = "sith.pytest" + [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "sith.settings" python_files = ["tests.py", "test_*.py", "*_tests.py"] diff --git a/sith/environ.py b/sith/environ.py new file mode 100644 index 00000000..0b048822 --- /dev/null +++ b/sith/environ.py @@ -0,0 +1,4 @@ +from environs import Env + +env = Env() +_ = env.read_env() diff --git a/sith/pytest.py b/sith/pytest.py new file mode 100644 index 00000000..3a99a7fc --- /dev/null +++ b/sith/pytest.py @@ -0,0 +1,23 @@ +import pytest + +from processes.composer import start_composer, stop_composer + +from .environ import env + +# pytest-django uses the load_initial_conftest hook +# it's the first hook loaded by pytest and can only +# be defined in a proper pytest plugin +# To use the composer before pytest-django loads +# we need to define this hook and thus create a proper +# pytest plugin. We can't just use conftest.py + + +@pytest.hookimpl(tryfirst=True) +def pytest_load_initial_conftests(early_config, parser, args): + """Hook that loads the composer before the pytest-django plugin""" + if (procfile := env.str("PROCFILE_PYTEST", None)) is not None: + start_composer(procfile) + + +def pytest_unconfigure(config): + stop_composer() diff --git a/sith/settings.py b/sith/settings.py index 8191251f..22ccf09e 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -42,14 +42,11 @@ from pathlib import Path import sentry_sdk from dateutil.relativedelta import relativedelta from django.utils.translation import gettext_lazy as _ -from environs import Env from sentry_sdk.integrations.django import DjangoIntegration +from .environ import env from .honeypot import custom_honeypot_error -env = Env() -env.read_env() - BASE_DIR = Path(__file__).parent.parent.resolve() # Quick-start development settings - unsuitable for production diff --git a/staticfiles/management/commands/runserver.py b/staticfiles/management/commands/runserver.py index 5fc80a64..8dd88538 100644 --- a/staticfiles/management/commands/runserver.py +++ b/staticfiles/management/commands/runserver.py @@ -1,13 +1,6 @@ -import logging -import os -import subprocess -import sys - -from django.conf import settings from django.contrib.staticfiles.management.commands.runserver import ( Command as Runserver, ) -from django.utils.autoreload import DJANGO_AUTORELOAD_ENV from staticfiles.processors import OpenApi @@ -16,19 +9,5 @@ class Command(Runserver): """Light wrapper around default runserver that integrates javascirpt auto bundling.""" def run(self, **options): - # OpenApi generation needs to be before the bundler OpenApi.compile() - # Run all other web processes but only if debug mode is enabled - # Also protects from re-launching the server if django reloads it - if os.environ.get(DJANGO_AUTORELOAD_ENV) is None: - logging.getLogger("django").info("Running complementary processes") - with subprocess.Popen( - [sys.executable, "-m", "honcho", "start"], - env={ - **os.environ, - **{"BUNDLER_MODE": "serve" if settings.DEBUG else "compile"}, - }, - ): - super().run(**options) - return super().run(**options) diff --git a/uv.lock b/uv.lock index 69a94a95..11b00547 100644 --- a/uv.lock +++ b/uv.lock @@ -1104,6 +1104,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, ] +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, +] + [[package]] name = "psycopg" version = "3.2.3" @@ -1520,6 +1535,7 @@ dependencies = [ { name = "mistune" }, { name = "phonenumbers" }, { name = "pillow" }, + { name = "psutil" }, { name = "pydantic-extra-types" }, { name = "python-dateutil" }, { name = "redis", extra = ["hiredis"] }, @@ -1581,6 +1597,7 @@ requires-dist = [ { name = "mistune", specifier = ">=3.0.2,<4.0.0" }, { name = "phonenumbers", specifier = ">=8.13.52,<9.0.0" }, { name = "pillow", specifier = ">=11.0.0,<12.0.0" }, + { name = "psutil", specifier = ">=7.0.0" }, { name = "pydantic-extra-types", specifier = ">=2.10.1,<3.0.0" }, { name = "python-dateutil", specifier = ">=2.9.0.post0,<3.0.0.0" }, { name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<6.0.0" }, From 3b80b36ed6acb536dcc1a614604dfe03b4dd8309 Mon Sep 17 00:00:00 2001 From: Sli Date: Mon, 17 Feb 2025 16:20:33 +0100 Subject: [PATCH 07/56] Update doc --- docs/tutorial/install-advanced.md | 24 ++++++++++++++++ docs/tutorial/install.md | 13 ++++----- docs/tutorial/structure.md | 47 ++++++++++++++++++------------- sith/settings.py | 3 +- 4 files changed, 57 insertions(+), 30 deletions(-) diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md index 7b4fe493..a46cbd6e 100644 --- a/docs/tutorial/install-advanced.md +++ b/docs/tutorial/install-advanced.md @@ -77,6 +77,30 @@ uv sync --group prod C'est parce que ces dépendances compilent certains modules à l'installation. +## Configurer Redis + +Redis est installé comme dépendance mais pas lancé par défaut. + +En mode développement, le sith se charge de le démarrer mais +pas en production ! + +Il faut donc lancer le service comme ceci: + +```bash +sudo systemctl start redis +sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot +``` + +Puis modifiez votre `.env` pour y configurer le bon port redis. +Le port du fichier d'exemple est un port non standard pour éviter +les conflits avec les instances de redis déjà en fonctionnement. + +```dotenv +REDIS_PORT=6379 +CACHE_URL=redis://127.0.0.1:${REDIS_PORT}/0 +``` + + ## Configurer PostgreSQL PostgreSQL est utilisé comme base de données. diff --git a/docs/tutorial/install.md b/docs/tutorial/install.md index 0a621587..a4635c02 100644 --- a/docs/tutorial/install.md +++ b/docs/tutorial/install.md @@ -100,14 +100,6 @@ cd /mnt//vos/fichiers/comme/dhab Python ne fait pas parti des dépendances puisqu'il est automatiquement installé par uv. -Parmi les dépendances installées se trouve redis (que nous utilisons comme cache). -Redis est un service qui doit être activé pour être utilisé. -Pour cela, effectuez les commandes : - -```bash -sudo systemctl start redis -sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot -``` ## Finaliser l'installation @@ -179,6 +171,11 @@ uv run ./manage.py runserver [http://localhost:8000](http://localhost:8000) ou bien [http://127.0.0.1:8000/](http://127.0.0.1:8000/). +!!!note + + Le serveur de développement se charge de lancer redis + et les autres services nécessaires au bon fonctionnement du site. + !!!tip Vous trouverez également, à l'adresse diff --git a/docs/tutorial/structure.md b/docs/tutorial/structure.md index 4ba0077e..6ab36c6c 100644 --- a/docs/tutorial/structure.md +++ b/docs/tutorial/structure.md @@ -66,21 +66,24 @@ sith/ │ └── ... ├── staticfiles/ (23) │ └── ... +├── processes/ (24) +│ └── ... │ -├── .coveragerc (24) -├── .envrc (25) +├── .coveragerc (25) +├── .envrc (26) ├── .gitattributes ├── .gitignore ├── .mailmap -├── .env (26) -├── .env.example (27) -├── manage.py (28) -├── mkdocs.yml (29) +├── .env (27) +├── .env.example (28) +├── manage.py (29) +├── mkdocs.yml (30) ├── uv.lock -├── pyproject.toml (30) -├── .venv/ (31) -├── .python-version (32) -├── Procfile (33) +├── pyproject.toml (31) +├── .venv/ (32) +├── .python-version (33) +├── Procfile.dev (34) +├── Procfile.pytest (35) └── README.md ``` @@ -122,23 +125,27 @@ sith/ 23. Gestion des statics du site. Override le système de statics de Django. Ajoute l'intégration du scss et du bundler js de manière transparente pour l'utilisateur. -24. Fichier de configuration de coverage. -25. Fichier de configuration de direnv. -26. Contient les variables d'environnement, qui sont susceptibles +24. Module de gestion des services externes. + Offre une API simple pour utiliser les fichiers `Procfile.*`. +25. Fichier de configuration de coverage. +26. Fichier de configuration de direnv. +27. Contient les variables d'environnement, qui sont susceptibles de varier d'une machine à l'autre. -27. Contient des valeurs par défaut pour le `.env` +28. Contient des valeurs par défaut pour le `.env` pouvant convenir à un environnment de développement local -28. Fichier généré automatiquement par Django. C'est lui +29. Fichier généré automatiquement par Django. C'est lui qui permet d'appeler des commandes de gestion du projet avec la syntaxe `python ./manage.py ` -29. Le fichier de configuration de la documentation, +30. Le fichier de configuration de la documentation, avec ses plugins et sa table des matières. -30. Le fichier où sont déclarés les dépendances et la configuration +31. Le fichier où sont déclarés les dépendances et la configuration de certaines d'entre elles. -31. Dossier d'environnement virtuel généré par uv -32. Fichier qui contrôle quelle version de python utiliser pour le projet -33. Fichier qui contrôle les services additionnels à lancer +32. Dossier d'environnement virtuel généré par uv +33. Fichier qui contrôle quelle version de python utiliser pour le projet +34. Fichier qui contrôle les services additionnels à lancer avec le serveur de développement +35. Fichier qui contrôle les services additionnels à lancer + avec pytest ## L'application principale diff --git a/sith/settings.py b/sith/settings.py index 22ccf09e..b283c3e6 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -216,8 +216,7 @@ DATABASES = { "default": env.dj_db_url("DATABASE_URL", conn_max_age=None, conn_health_checks=True) } -if "CACHE_URL" in os.environ: - CACHES = {"default": env.dj_cache_url("CACHE_URL")} +CACHES = {"default": env.dj_cache_url("CACHE_URL")} SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" From 7f8304e407876d2f842b0b5d52ad2b28c46c2a25 Mon Sep 17 00:00:00 2001 From: Sli Date: Mon, 17 Feb 2025 16:30:25 +0100 Subject: [PATCH 08/56] Add redis to test pipeline --- .github/actions/setup_project/action.yml | 5 +++++ .github/workflows/ci.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/.github/actions/setup_project/action.yml b/.github/actions/setup_project/action.yml index 2d2aae89..bb10a2f5 100644 --- a/.github/actions/setup_project/action.yml +++ b/.github/actions/setup_project/action.yml @@ -9,6 +9,11 @@ runs: packages: gettext version: 1.0 # increment to reset cache + - name: Install Redis + uses: shogo82148/actions-setup-redis@v1 + with: + redis-version: "7.x" + - name: Install uv uses: astral-sh/setup-uv@v5 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa17e14c..554e8055 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: env: SECRET_KEY: notTheRealOne DATABASE_URL: sqlite:///db.sqlite3 + CACHE_URL: redis://127.0.0.1:6379/0 jobs: pre-commit: From aa66fc61ab4e77857a7c909ed728b785783ef901 Mon Sep 17 00:00:00 2001 From: Sli Date: Thu, 20 Feb 2025 18:38:15 +0100 Subject: [PATCH 09/56] Apply review comments --- docs/tutorial/install-advanced.md | 22 +++++++++++++++++++++- manage.py | 11 ++++------- pyproject.toml | 4 ++++ {processes => sith}/composer.py | 0 sith/environ.py | 4 ---- sith/pytest.py | 10 ++++------ sith/settings.py | 9 ++++++++- uv.lock | 2 +- 8 files changed, 42 insertions(+), 20 deletions(-) rename {processes => sith}/composer.py (100%) delete mode 100644 sith/environ.py diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md index a46cbd6e..897277c1 100644 --- a/docs/tutorial/install-advanced.md +++ b/docs/tutorial/install-advanced.md @@ -77,7 +77,22 @@ uv sync --group prod C'est parce que ces dépendances compilent certains modules à l'installation. -## Configurer Redis +## Désactiver Honcho + +Honcho est utilisé en développement pour simplifier la gestion +des services externes (redis, vite et autres futures). + +En mode production, il est nécessaire de le désactiver puisque normalement +tous ces services sont déjà configurés. + +Pour désactiver Honcho il suffit de sélectionner aucun `PROCFILE_` dans la config. + +```dotenv +PROCFILE_RUNSERVER= +PROCFILE_PYTEST= +``` + +## Configurer Redis en service externe Redis est installé comme dépendance mais pas lancé par défaut. @@ -100,6 +115,11 @@ REDIS_PORT=6379 CACHE_URL=redis://127.0.0.1:${REDIS_PORT}/0 ``` +Si on souhaite configurer redis pour communiquer via un socket : + +```dovenv +CACHE_URL=redis:///path/to/redis-server.sock +``` ## Configurer PostgreSQL diff --git a/manage.py b/manage.py index 71939ec3..444f6799 100755 --- a/manage.py +++ b/manage.py @@ -18,19 +18,16 @@ import sys from django.utils.autoreload import DJANGO_AUTORELOAD_ENV -from processes.composer import start_composer, stop_composer -from sith.environ import env +from sith.composer import start_composer, stop_composer +from sith.settings import PROCFILE_RUNSERVER if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sith.settings") from django.core.management import execute_from_command_line - if ( - os.environ.get(DJANGO_AUTORELOAD_ENV) is None - and (procfile := env.str("PROCFILE_RUNSERVER", None)) is not None - ): - start_composer(procfile) + if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and PROCFILE_RUNSERVER is not None: + start_composer(PROCFILE_RUNSERVER) execute_from_command_line(sys.argv) diff --git a/pyproject.toml b/pyproject.toml index d5d3c1f0..7be2162c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,6 +126,10 @@ ignore = [ [tool.ruff.lint.pydocstyle] convention = "google" +[build-system] # A build system is needed to register a pytest plugin +requires = ["hatchling"] +build-backend = "hatchling.build" + [project.entry-points.pytest11] sith = "sith.pytest" diff --git a/processes/composer.py b/sith/composer.py similarity index 100% rename from processes/composer.py rename to sith/composer.py diff --git a/sith/environ.py b/sith/environ.py deleted file mode 100644 index 0b048822..00000000 --- a/sith/environ.py +++ /dev/null @@ -1,4 +0,0 @@ -from environs import Env - -env = Env() -_ = env.read_env() diff --git a/sith/pytest.py b/sith/pytest.py index 3a99a7fc..29250e16 100644 --- a/sith/pytest.py +++ b/sith/pytest.py @@ -1,10 +1,8 @@ import pytest -from processes.composer import start_composer, stop_composer +from .composer import start_composer, stop_composer +from .settings import PROCFILE_PYTEST -from .environ import env - -# pytest-django uses the load_initial_conftest hook # it's the first hook loaded by pytest and can only # be defined in a proper pytest plugin # To use the composer before pytest-django loads @@ -15,8 +13,8 @@ from .environ import env @pytest.hookimpl(tryfirst=True) def pytest_load_initial_conftests(early_config, parser, args): """Hook that loads the composer before the pytest-django plugin""" - if (procfile := env.str("PROCFILE_PYTEST", None)) is not None: - start_composer(procfile) + if PROCFILE_PYTEST is not None: + start_composer(PROCFILE_PYTEST) def pytest_unconfigure(config): diff --git a/sith/settings.py b/sith/settings.py index b283c3e6..cc434703 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -42,13 +42,20 @@ from pathlib import Path import sentry_sdk from dateutil.relativedelta import relativedelta from django.utils.translation import gettext_lazy as _ +from environs import Env from sentry_sdk.integrations.django import DjangoIntegration -from .environ import env from .honeypot import custom_honeypot_error +env = Env() +env.read_env() + BASE_DIR = Path(__file__).parent.parent.resolve() +# Composer settings +PROCFILE_RUNSERVER = env.str("PROCFILE_RUNSERVER", None) +PROCFILE_PYTEST = env.str("PROCFILE_PYTEST", None) + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ diff --git a/uv.lock b/uv.lock index 11b00547..f96b078e 100644 --- a/uv.lock +++ b/uv.lock @@ -1513,7 +1513,7 @@ wheels = [ [[package]] name = "sith" version = "3" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "cryptography" }, { name = "dict2xml" }, From e542fe11b9c693312eb0b95cb3374fd5f57dbc89 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 26 Feb 2025 10:42:11 +0100 Subject: [PATCH 10/56] Add a pid file to avoid running honcho multiple times --- manage.py | 7 +++++-- sith/composer.py | 54 ++++++++++++++++++++++++++++++++++++++++++------ sith/pytest.py | 7 +++---- sith/settings.py | 3 +++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/manage.py b/manage.py index 444f6799..05877790 100755 --- a/manage.py +++ b/manage.py @@ -13,6 +13,8 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +import atexit +import logging import os import sys @@ -22,13 +24,14 @@ from sith.composer import start_composer, stop_composer from sith.settings import PROCFILE_RUNSERVER if __name__ == "__main__": + logging.basicConfig(encoding="utf-8", level=logging.INFO) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sith.settings") from django.core.management import execute_from_command_line if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and PROCFILE_RUNSERVER is not None: start_composer(PROCFILE_RUNSERVER) + _ = atexit.register(stop_composer) execute_from_command_line(sys.argv) - - stop_composer() diff --git a/sith/composer.py b/sith/composer.py index 8355e205..e0a97bbd 100644 --- a/sith/composer.py +++ b/sith/composer.py @@ -1,26 +1,68 @@ -import os +import logging import signal import subprocess import sys import psutil -COMPOSER_PID = "COMPOSER_PID" +from sith import settings + + +def get_pid() -> int | None: + """Read the PID file to get the currently running composer if it exists""" + if not settings.COMPOSER_PID_PATH.exists(): + return None + with open(settings.COMPOSER_PID_PATH, "r", encoding="utf8") as f: + return int(f.read()) + + +def write_pid(pid: int): + """Write currently running composer pid in PID file""" + if not settings.COMPOSER_PID_PATH.exists(): + settings.COMPOSER_PID_PATH.parent.mkdir(parents=True, exist_ok=True) + with open(settings.COMPOSER_PID_PATH, "w", encoding="utf8") as f: + _ = f.write(str(pid)) + + +def delete_pid(): + """Delete PID file for cleanup""" + settings.COMPOSER_PID_PATH.unlink(missing_ok=True) + + +def is_composer_running() -> bool: + """Check if the process in the PID file is running""" + pid = get_pid() + if pid is None: + return False + try: + return psutil.Process(pid).is_running() + except psutil.NoSuchProcess: + return False def start_composer(procfile: str): """Starts the composer and stores the PID as an environment variable This allows for running smoothly with the django reloader """ + if is_composer_running(): + logging.info(f"Composer is already running with pid {get_pid()}") + logging.info( + f"If this is a mistake, please delete {settings.COMPOSER_PID_PATH} and restart the process" + ) + return process = subprocess.Popen( [sys.executable, "-m", "honcho", "-f", procfile, "start"], ) - os.environ[COMPOSER_PID] = str(process.pid) + write_pid(process.pid) def stop_composer(): """Stops the composer if it was started before""" - if (pid := os.environ.get(COMPOSER_PID, None)) is not None: - process = psutil.Process(int(pid)) + if is_composer_running(): + process = psutil.Process(get_pid()) + if process.parent() != psutil.Process(): + logging.info("Currently running composer is controlled by another process") + return process.send_signal(signal.SIGTERM) - process.wait() + _ = process.wait() + delete_pid() diff --git a/sith/pytest.py b/sith/pytest.py index 29250e16..731cd584 100644 --- a/sith/pytest.py +++ b/sith/pytest.py @@ -1,3 +1,5 @@ +import atexit + import pytest from .composer import start_composer, stop_composer @@ -15,7 +17,4 @@ def pytest_load_initial_conftests(early_config, parser, args): """Hook that loads the composer before the pytest-django plugin""" if PROCFILE_PYTEST is not None: start_composer(PROCFILE_PYTEST) - - -def pytest_unconfigure(config): - stop_composer() + _ = atexit.register(stop_composer) diff --git a/sith/settings.py b/sith/settings.py index cc434703..b9acd61d 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -56,6 +56,9 @@ BASE_DIR = Path(__file__).parent.parent.resolve() PROCFILE_RUNSERVER = env.str("PROCFILE_RUNSERVER", None) PROCFILE_PYTEST = env.str("PROCFILE_PYTEST", None) +## File path used to avoid running the composer multiple times at the same time +COMPOSER_PID_PATH = env.path("COMPOSER_PID_PATH", BASE_DIR / "composer.pid") + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ From 728ad157e97d6207fa76dde82047c5b2ba7dcfa3 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 26 Feb 2025 14:37:22 +0100 Subject: [PATCH 11/56] Apply review comments --- .gitignore | 2 +- docs/tutorial/install-advanced.md | 10 +++++++++- sith/composer.py | 5 +++-- sith/settings.py | 15 +++++++++++++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index eb78b41d..65bd0e2e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,10 +18,10 @@ sith/search_indexes/ .coverage coverage_report/ node_modules/ +.env # compiled documentation site/ -.env ### Redis ### diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md index 897277c1..9fc54a8a 100644 --- a/docs/tutorial/install-advanced.md +++ b/docs/tutorial/install-advanced.md @@ -85,13 +85,21 @@ des services externes (redis, vite et autres futures). En mode production, il est nécessaire de le désactiver puisque normalement tous ces services sont déjà configurés. -Pour désactiver Honcho il suffit de sélectionner aucun `PROCFILE_` dans la config. +Pour désactiver Honcho il suffit de ne sélectionner aucun `PROCFILE_` dans la config. ```dotenv PROCFILE_RUNSERVER= PROCFILE_PYTEST= ``` +!!! note + + Si honcho ne tourne plus, la recompilation automatique + des fichiers statiques ne se fait plus. + Si vous en avez besoin et que vous travaillez sans honcho, + vous devez ouvrir une autre fenêtre de votre terminal + et lancer la commande `npm run serve` + ## Configurer Redis en service externe Redis est installé comme dépendance mais pas lancé par défaut. diff --git a/sith/composer.py b/sith/composer.py index e0a97bbd..b1d90d92 100644 --- a/sith/composer.py +++ b/sith/composer.py @@ -2,6 +2,7 @@ import logging import signal import subprocess import sys +from pathlib import Path import psutil @@ -40,7 +41,7 @@ def is_composer_running() -> bool: return False -def start_composer(procfile: str): +def start_composer(procfile: Path): """Starts the composer and stores the PID as an environment variable This allows for running smoothly with the django reloader """ @@ -51,7 +52,7 @@ def start_composer(procfile: str): ) return process = subprocess.Popen( - [sys.executable, "-m", "honcho", "-f", procfile, "start"], + [sys.executable, "-m", "honcho", "-f", str(procfile), "start"], ) write_pid(process.pid) diff --git a/sith/settings.py b/sith/settings.py index b9acd61d..a95f6725 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -50,11 +50,22 @@ from .honeypot import custom_honeypot_error env = Env() env.read_env() + +@env.parser_for("optional_file") +def optional_file_parser(value: str) -> Path | None: + if not value: + return None + path = Path(value) + if not path.is_file(): + return None + return path + + BASE_DIR = Path(__file__).parent.parent.resolve() # Composer settings -PROCFILE_RUNSERVER = env.str("PROCFILE_RUNSERVER", None) -PROCFILE_PYTEST = env.str("PROCFILE_PYTEST", None) +PROCFILE_RUNSERVER = env.optional_file("PROCFILE_RUNSERVER", None) +PROCFILE_PYTEST = env.optional_file("PROCFILE_PYTEST", None) ## File path used to avoid running the composer multiple times at the same time COMPOSER_PID_PATH = env.path("COMPOSER_PID_PATH", BASE_DIR / "composer.pid") From 75c4c55a32d86b014178b0ed123bf9b0174d3ef5 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 4 Mar 2025 11:59:35 +0100 Subject: [PATCH 12/56] Only run full procfile on runserver --- .env.example | 6 +++--- .gitignore | 1 + Procfile.dev => Procfile.full | 0 Procfile.pytest => Procfile.minimal | 0 docs/tutorial/install-advanced.md | 4 ++-- docs/tutorial/structure.md | 8 ++++---- manage.py | 7 ++++--- sith/pytest.py | 6 +++--- sith/settings.py | 4 ++-- 9 files changed, 19 insertions(+), 17 deletions(-) rename Procfile.dev => Procfile.full (100%) rename Procfile.pytest => Procfile.minimal (100%) diff --git a/.env.example b/.env.example index da4d2ac8..1f050d4a 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,6 @@ REDIS_PORT=7963 CACHE_URL=redis://127.0.0.1:${REDIS_PORT}/0 # Used to select which other services to run alongside -# runserver and pytest -PROCFILE_RUNSERVER=Procfile.dev -PROCFILE_PYTEST=Procfile.pytest +# manage.py, pytest and runserver +PROCFILE_FULL=Procfile.full +PROCFILE_MINIMAL=Procfile.minimal diff --git a/.gitignore b/.gitignore index 65bd0e2e..ecda5902 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ sith/search_indexes/ coverage_report/ node_modules/ .env +*.pid # compiled documentation site/ diff --git a/Procfile.dev b/Procfile.full similarity index 100% rename from Procfile.dev rename to Procfile.full diff --git a/Procfile.pytest b/Procfile.minimal similarity index 100% rename from Procfile.pytest rename to Procfile.minimal diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md index 9fc54a8a..b5630d2b 100644 --- a/docs/tutorial/install-advanced.md +++ b/docs/tutorial/install-advanced.md @@ -88,8 +88,8 @@ tous ces services sont déjà configurés. Pour désactiver Honcho il suffit de ne sélectionner aucun `PROCFILE_` dans la config. ```dotenv -PROCFILE_RUNSERVER= -PROCFILE_PYTEST= +PROCFILE_FULL= +PROCFILE_MINIMAL= ``` !!! note diff --git a/docs/tutorial/structure.md b/docs/tutorial/structure.md index 6ab36c6c..6af76354 100644 --- a/docs/tutorial/structure.md +++ b/docs/tutorial/structure.md @@ -82,8 +82,8 @@ sith/ ├── pyproject.toml (31) ├── .venv/ (32) ├── .python-version (33) -├── Procfile.dev (34) -├── Procfile.pytest (35) +├── Procfile.full (34) +├── Procfile.minimal (35) └── README.md ``` @@ -143,9 +143,9 @@ sith/ 32. Dossier d'environnement virtuel généré par uv 33. Fichier qui contrôle quelle version de python utiliser pour le projet 34. Fichier qui contrôle les services additionnels à lancer - avec le serveur de développement + avec le serveur de développement. Serveurs + gestion des statics. 35. Fichier qui contrôle les services additionnels à lancer - avec pytest + avec les autres commandes (pytest, shell…). Serveurs uniquement. ## L'application principale diff --git a/manage.py b/manage.py index 05877790..cb26d55d 100755 --- a/manage.py +++ b/manage.py @@ -21,7 +21,7 @@ import sys from django.utils.autoreload import DJANGO_AUTORELOAD_ENV from sith.composer import start_composer, stop_composer -from sith.settings import PROCFILE_RUNSERVER +from sith.settings import PROCFILE_FULL, PROCFILE_MINIMAL if __name__ == "__main__": logging.basicConfig(encoding="utf-8", level=logging.INFO) @@ -30,8 +30,9 @@ if __name__ == "__main__": from django.core.management import execute_from_command_line - if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and PROCFILE_RUNSERVER is not None: - start_composer(PROCFILE_RUNSERVER) + procfile = PROCFILE_FULL if sys.argv[1] == "runserver" else PROCFILE_MINIMAL + if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and procfile is not None: + start_composer(procfile) _ = atexit.register(stop_composer) execute_from_command_line(sys.argv) diff --git a/sith/pytest.py b/sith/pytest.py index 731cd584..844f49d2 100644 --- a/sith/pytest.py +++ b/sith/pytest.py @@ -3,7 +3,7 @@ import atexit import pytest from .composer import start_composer, stop_composer -from .settings import PROCFILE_PYTEST +from .settings import PROCFILE_MINIMAL # it's the first hook loaded by pytest and can only # be defined in a proper pytest plugin @@ -15,6 +15,6 @@ from .settings import PROCFILE_PYTEST @pytest.hookimpl(tryfirst=True) def pytest_load_initial_conftests(early_config, parser, args): """Hook that loads the composer before the pytest-django plugin""" - if PROCFILE_PYTEST is not None: - start_composer(PROCFILE_PYTEST) + if PROCFILE_MINIMAL is not None: + start_composer(PROCFILE_MINIMAL) _ = atexit.register(stop_composer) diff --git a/sith/settings.py b/sith/settings.py index a95f6725..dab15139 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -64,8 +64,8 @@ def optional_file_parser(value: str) -> Path | None: BASE_DIR = Path(__file__).parent.parent.resolve() # Composer settings -PROCFILE_RUNSERVER = env.optional_file("PROCFILE_RUNSERVER", None) -PROCFILE_PYTEST = env.optional_file("PROCFILE_PYTEST", None) +PROCFILE_FULL = env.optional_file("PROCFILE_FULL", None) +PROCFILE_MINIMAL = env.optional_file("PROCFILE_MINIMAL", None) ## File path used to avoid running the composer multiple times at the same time COMPOSER_PID_PATH = env.path("COMPOSER_PID_PATH", BASE_DIR / "composer.pid") From 87f790a0441fabf70b09db1b28b22920f30f68a1 Mon Sep 17 00:00:00 2001 From: imperosol Date: Mon, 3 Mar 2025 13:17:53 +0100 Subject: [PATCH 13/56] Don't minify statics in debug mode --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f46df7db..bc6018b1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "compile": "vite build --mode production", "compile-dev": "vite build --mode development", - "serve": "vite build --mode development --watch", + "serve": "vite build --mode development --watch --minify false", "analyse-dev": "vite-bundle-visualizer --mode development", "analyse-prod": "vite-bundle-visualizer --mode production", "check": "biome check --write" From 6b27a97e7b248c6c544ef03b014c02259a20af82 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 4 Mar 2025 14:48:44 +0100 Subject: [PATCH 14/56] Launch multiple honcho files depending on the context --- .env.example | 4 +- Procfile.full | 2 - Procfile.minimal => Procfile.service | 0 Procfile.static | 1 + docs/tutorial/install-advanced.md | 8 +-- docs/tutorial/structure.md | 12 ++--- manage.py | 9 ++-- sith/composer.py | 51 ++++++++++++-------- sith/pytest.py | 8 +-- sith/settings.py | 7 +-- staticfiles/management/commands/runserver.py | 12 +++++ 11 files changed, 66 insertions(+), 48 deletions(-) delete mode 100644 Procfile.full rename Procfile.minimal => Procfile.service (100%) create mode 100644 Procfile.static diff --git a/.env.example b/.env.example index 1f050d4a..2d47ad1f 100644 --- a/.env.example +++ b/.env.example @@ -13,5 +13,5 @@ CACHE_URL=redis://127.0.0.1:${REDIS_PORT}/0 # Used to select which other services to run alongside # manage.py, pytest and runserver -PROCFILE_FULL=Procfile.full -PROCFILE_MINIMAL=Procfile.minimal +PROCFILE_STATIC=Procfile.static +PROCFILE_SERVICE=Procfile.service diff --git a/Procfile.full b/Procfile.full deleted file mode 100644 index 31652ef9..00000000 --- a/Procfile.full +++ /dev/null @@ -1,2 +0,0 @@ -bundler: npm run serve -redis: redis-server --port $REDIS_PORT \ No newline at end of file diff --git a/Procfile.minimal b/Procfile.service similarity index 100% rename from Procfile.minimal rename to Procfile.service diff --git a/Procfile.static b/Procfile.static new file mode 100644 index 00000000..857b2f2d --- /dev/null +++ b/Procfile.static @@ -0,0 +1 @@ +bundler: npm run serve \ No newline at end of file diff --git a/docs/tutorial/install-advanced.md b/docs/tutorial/install-advanced.md index b5630d2b..2da2fc42 100644 --- a/docs/tutorial/install-advanced.md +++ b/docs/tutorial/install-advanced.md @@ -88,15 +88,15 @@ tous ces services sont déjà configurés. Pour désactiver Honcho il suffit de ne sélectionner aucun `PROCFILE_` dans la config. ```dotenv -PROCFILE_FULL= -PROCFILE_MINIMAL= +PROCFILE_STATIC= +PROCFILE_SERVICE= ``` !!! note - Si honcho ne tourne plus, la recompilation automatique + Si `PROCFILE_STATIC` est désactivé, la recompilation automatique des fichiers statiques ne se fait plus. - Si vous en avez besoin et que vous travaillez sans honcho, + Si vous en avez besoin et que vous travaillez sans `PROCFILE_STATIC`, vous devez ouvrir une autre fenêtre de votre terminal et lancer la commande `npm run serve` diff --git a/docs/tutorial/structure.md b/docs/tutorial/structure.md index 6af76354..7c740bde 100644 --- a/docs/tutorial/structure.md +++ b/docs/tutorial/structure.md @@ -82,8 +82,8 @@ sith/ ├── pyproject.toml (31) ├── .venv/ (32) ├── .python-version (33) -├── Procfile.full (34) -├── Procfile.minimal (35) +├── Procfile.static (34) +├── Procfile.service (35) └── README.md ``` @@ -142,10 +142,10 @@ sith/ de certaines d'entre elles. 32. Dossier d'environnement virtuel généré par uv 33. Fichier qui contrôle quelle version de python utiliser pour le projet -34. Fichier qui contrôle les services additionnels à lancer - avec le serveur de développement. Serveurs + gestion des statics. -35. Fichier qui contrôle les services additionnels à lancer - avec les autres commandes (pytest, shell…). Serveurs uniquement. +34. Fichier qui contrôle les commandes à lancer pour gérer la compilation + automatique des static et autres services nécessaires à la command runserver. +35. Fichier qui contrôle les services tiers nécessaires au fonctionnement + du Sith tel que redis. ## L'application principale diff --git a/manage.py b/manage.py index cb26d55d..101696e2 100755 --- a/manage.py +++ b/manage.py @@ -21,7 +21,7 @@ import sys from django.utils.autoreload import DJANGO_AUTORELOAD_ENV from sith.composer import start_composer, stop_composer -from sith.settings import PROCFILE_FULL, PROCFILE_MINIMAL +from sith.settings import PROCFILE_SERVICE if __name__ == "__main__": logging.basicConfig(encoding="utf-8", level=logging.INFO) @@ -30,9 +30,8 @@ if __name__ == "__main__": from django.core.management import execute_from_command_line - procfile = PROCFILE_FULL if sys.argv[1] == "runserver" else PROCFILE_MINIMAL - if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and procfile is not None: - start_composer(procfile) - _ = atexit.register(stop_composer) + if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and PROCFILE_SERVICE is not None: + start_composer(PROCFILE_SERVICE) + _ = atexit.register(stop_composer, procfile=PROCFILE_SERVICE) execute_from_command_line(sys.argv) diff --git a/sith/composer.py b/sith/composer.py index b1d90d92..dd3d648a 100644 --- a/sith/composer.py +++ b/sith/composer.py @@ -9,30 +9,37 @@ import psutil from sith import settings -def get_pid() -> int | None: +def get_pid_file(procfile: Path) -> Path: + """Get the PID file associated with a procfile""" + return settings.BASE_DIR / procfile.with_suffix(f"{procfile.suffix}.pid") + + +def get_pid(procfile: Path) -> int | None: """Read the PID file to get the currently running composer if it exists""" - if not settings.COMPOSER_PID_PATH.exists(): + file = get_pid_file(procfile) + if not file.exists(): return None - with open(settings.COMPOSER_PID_PATH, "r", encoding="utf8") as f: + with open(file, "r", encoding="utf8") as f: return int(f.read()) -def write_pid(pid: int): +def write_pid(procfile: Path, pid: int): """Write currently running composer pid in PID file""" - if not settings.COMPOSER_PID_PATH.exists(): - settings.COMPOSER_PID_PATH.parent.mkdir(parents=True, exist_ok=True) - with open(settings.COMPOSER_PID_PATH, "w", encoding="utf8") as f: + file = get_pid_file(procfile) + if not file.exists(): + file.parent.mkdir(parents=True, exist_ok=True) + with open(file, "w", encoding="utf8") as f: _ = f.write(str(pid)) -def delete_pid(): +def delete_pid(procfile: Path): """Delete PID file for cleanup""" - settings.COMPOSER_PID_PATH.unlink(missing_ok=True) + get_pid_file(procfile).unlink(missing_ok=True) -def is_composer_running() -> bool: +def is_composer_running(procfile: Path) -> bool: """Check if the process in the PID file is running""" - pid = get_pid() + pid = get_pid(procfile) if pid is None: return False try: @@ -45,25 +52,29 @@ def start_composer(procfile: Path): """Starts the composer and stores the PID as an environment variable This allows for running smoothly with the django reloader """ - if is_composer_running(): - logging.info(f"Composer is already running with pid {get_pid()}") + if is_composer_running(procfile): logging.info( - f"If this is a mistake, please delete {settings.COMPOSER_PID_PATH} and restart the process" + f"Composer for {procfile} is already running with pid {get_pid(procfile)}" + ) + logging.info( + f"If this is a mistake, please delete {get_pid_file(procfile)} and restart the process" ) return process = subprocess.Popen( [sys.executable, "-m", "honcho", "-f", str(procfile), "start"], ) - write_pid(process.pid) + write_pid(procfile, process.pid) -def stop_composer(): +def stop_composer(procfile: Path): """Stops the composer if it was started before""" - if is_composer_running(): - process = psutil.Process(get_pid()) + if is_composer_running(procfile): + process = psutil.Process(get_pid(procfile)) if process.parent() != psutil.Process(): - logging.info("Currently running composer is controlled by another process") + logging.info( + f"Currently running composer for {procfile} is controlled by another process" + ) return process.send_signal(signal.SIGTERM) _ = process.wait() - delete_pid() + delete_pid(procfile) diff --git a/sith/pytest.py b/sith/pytest.py index 844f49d2..f3cb4ec6 100644 --- a/sith/pytest.py +++ b/sith/pytest.py @@ -3,7 +3,7 @@ import atexit import pytest from .composer import start_composer, stop_composer -from .settings import PROCFILE_MINIMAL +from .settings import PROCFILE_SERVICE # it's the first hook loaded by pytest and can only # be defined in a proper pytest plugin @@ -15,6 +15,6 @@ from .settings import PROCFILE_MINIMAL @pytest.hookimpl(tryfirst=True) def pytest_load_initial_conftests(early_config, parser, args): """Hook that loads the composer before the pytest-django plugin""" - if PROCFILE_MINIMAL is not None: - start_composer(PROCFILE_MINIMAL) - _ = atexit.register(stop_composer) + if PROCFILE_SERVICE is not None: + start_composer(PROCFILE_SERVICE) + _ = atexit.register(stop_composer, procfile=PROCFILE_SERVICE) diff --git a/sith/settings.py b/sith/settings.py index dab15139..f75b1ac9 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -64,11 +64,8 @@ def optional_file_parser(value: str) -> Path | None: BASE_DIR = Path(__file__).parent.parent.resolve() # Composer settings -PROCFILE_FULL = env.optional_file("PROCFILE_FULL", None) -PROCFILE_MINIMAL = env.optional_file("PROCFILE_MINIMAL", None) - -## File path used to avoid running the composer multiple times at the same time -COMPOSER_PID_PATH = env.path("COMPOSER_PID_PATH", BASE_DIR / "composer.pid") +PROCFILE_STATIC = env.optional_file("PROCFILE_STATIC", None) +PROCFILE_SERVICE = env.optional_file("PROCFILE_SERVICE", None) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ diff --git a/staticfiles/management/commands/runserver.py b/staticfiles/management/commands/runserver.py index 8dd88538..7138dee1 100644 --- a/staticfiles/management/commands/runserver.py +++ b/staticfiles/management/commands/runserver.py @@ -1,7 +1,13 @@ +import atexit +import os + +from django.conf import settings from django.contrib.staticfiles.management.commands.runserver import ( Command as Runserver, ) +from django.utils.autoreload import DJANGO_AUTORELOAD_ENV +from sith.composer import start_composer, stop_composer from staticfiles.processors import OpenApi @@ -10,4 +16,10 @@ class Command(Runserver): def run(self, **options): OpenApi.compile() + if ( + os.environ.get(DJANGO_AUTORELOAD_ENV) is None + and settings.PROCFILE_STATIC is not None + ): + start_composer(settings.PROCFILE_STATIC) + _ = atexit.register(stop_composer, procfile=settings.PROCFILE_STATIC) super().run(**options) From 99915072975934817a2ab6830e74dce631cd580c Mon Sep 17 00:00:00 2001 From: Kenneth SOARES Date: Wed, 5 Mar 2025 19:59:28 +0100 Subject: [PATCH 15/56] made country flags apply to windows in chrome browsers --- core/static/bundled/polyfill-index.ts | 5 +++++ core/static/core/header.scss | 1 + core/templates/core/base.jinja | 1 + package-lock.json | 6 ++++++ package.json | 5 ++++- 5 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 core/static/bundled/polyfill-index.ts diff --git a/core/static/bundled/polyfill-index.ts b/core/static/bundled/polyfill-index.ts new file mode 100644 index 00000000..96871309 --- /dev/null +++ b/core/static/bundled/polyfill-index.ts @@ -0,0 +1,5 @@ +import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; + +window.addEventListener("DOMContentLoaded", () => { + polyfillCountryFlagEmojis(); +}); diff --git a/core/static/core/header.scss b/core/static/core/header.scss index fd2cae8c..7cac3a3e 100644 --- a/core/static/core/header.scss +++ b/core/static/core/header.scss @@ -106,6 +106,7 @@ $hovered-red-text-color: #ff4d4d; color: $text-color; font-weight: normal; line-height: 1.3em; + font-family: "Twemoji Country Flags", sans-serif; &:hover { background-color: $background-color-hovered; diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index ef184fbf..01f75d12 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -23,6 +23,7 @@ + diff --git a/package-lock.json b/package-lock.json index 012e48e7..f4d6a23c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "3d-force-graph": "^1.73.4", "alpinejs": "^3.14.7", "chart.js": "^4.4.4", + "country-flag-emoji-polyfill": "^0.1.8", "cytoscape": "^3.30.2", "cytoscape-cxtmenu": "^3.5.0", "cytoscape-klay": "^3.1.4", @@ -3378,6 +3379,11 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/country-flag-emoji-polyfill": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/country-flag-emoji-polyfill/-/country-flag-emoji-polyfill-0.1.8.tgz", + "integrity": "sha512-Mbah52sADS3gshUYhK5142gtUuJpHYOXlXtLFI3Ly4RqgkmPMvhX9kMZSTqDM8P7UqtSW99eHKFphhQSGXA3Cg==" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/package.json b/package.json index 93494819..5cd4c931 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "keywords": [], "author": "", "license": "GPL-3.0-only", - "sideEffects": [".css"], + "sideEffects": [ + ".css" + ], "imports": { "#openapi": "./staticfiles/generated/openapi/index.ts", "#core:*": "./core/static/bundled/*", @@ -48,6 +50,7 @@ "3d-force-graph": "^1.73.4", "alpinejs": "^3.14.7", "chart.js": "^4.4.4", + "country-flag-emoji-polyfill": "^0.1.8", "cytoscape": "^3.30.2", "cytoscape-cxtmenu": "^3.5.0", "cytoscape-klay": "^3.1.4", From bf0779a096a6e5722b4ad52f34e71ff4440fed45 Mon Sep 17 00:00:00 2001 From: Kenneth SOARES Date: Wed, 5 Mar 2025 20:04:20 +0100 Subject: [PATCH 16/56] fix formatting --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 5cd4c931..ccac56fc 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,7 @@ "keywords": [], "author": "", "license": "GPL-3.0-only", - "sideEffects": [ - ".css" - ], + "sideEffects": [".css"], "imports": { "#openapi": "./staticfiles/generated/openapi/index.ts", "#core:*": "./core/static/bundled/*", From bff6513192a52de8eadb6f76a1efb373d0eff967 Mon Sep 17 00:00:00 2001 From: Kenneth SOARES Date: Wed, 5 Mar 2025 20:13:20 +0100 Subject: [PATCH 17/56] renamed polyfill-index.ts to country-flags-index.ts --- .../bundled/{polyfill-index.ts => country-flags-index.ts} | 0 core/templates/core/base.jinja | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename core/static/bundled/{polyfill-index.ts => country-flags-index.ts} (100%) diff --git a/core/static/bundled/polyfill-index.ts b/core/static/bundled/country-flags-index.ts similarity index 100% rename from core/static/bundled/polyfill-index.ts rename to core/static/bundled/country-flags-index.ts diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 01f75d12..6ee285b2 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -23,7 +23,7 @@ - + From 62246f342d225af58fcd83f0f85e1a3777b447ae Mon Sep 17 00:00:00 2001 From: Kenneth SOARES Date: Wed, 5 Mar 2025 20:19:11 +0100 Subject: [PATCH 18/56] removed unnecessary event listener --- core/static/bundled/country-flags-index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/static/bundled/country-flags-index.ts b/core/static/bundled/country-flags-index.ts index 96871309..1dc005c3 100644 --- a/core/static/bundled/country-flags-index.ts +++ b/core/static/bundled/country-flags-index.ts @@ -1,5 +1,3 @@ import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; -window.addEventListener("DOMContentLoaded", () => { - polyfillCountryFlagEmojis(); -}); +polyfillCountryFlagEmojis(); From d10393ea3713f3109e286f6a12aafceb65dbadf5 Mon Sep 17 00:00:00 2001 From: imperosol Date: Sun, 9 Mar 2025 15:04:53 +0100 Subject: [PATCH 19/56] update dependencies --- .pre-commit-config.yaml | 2 +- pyproject.toml | 48 ++-- uv.lock | 532 ++++++++++++++++++++-------------------- 3 files changed, 287 insertions(+), 295 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 506d9ebe..8d72985b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.8.3 + rev: v0.9.10 hooks: - id: ruff # just check the code, and print the errors - id: ruff # actually fix the fixable errors, but print nothing diff --git a/pyproject.toml b/pyproject.toml index 7be2162c..05246070 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,31 +19,31 @@ authors = [ license = {text = "GPL-3.0-only"} requires-python = "<4.0,>=3.12" dependencies = [ - "Django<5.0.0,>=4.2.17", + "django<5.0.0,>=4.2.20", "django-ninja<2.0.0,>=1.3.0", - "django-ninja-extra<1.0.0,>=0.21.8", - "Pillow<12.0.0,>=11.0.0", - "mistune<4.0.0,>=3.0.2", + "django-ninja-extra<1.0.0,>=0.22.4", + "Pillow<12.0.0,>=11.1.0", + "mistune<4.0.0,>=3.1.2", "django-jinja<3.0.0,>=2.11.0", - "cryptography<45.0.0,>=44.0.0", + "cryptography<45.0.0,>=44.0.2", "django-phonenumber-field<9.0.0,>=8.0.0", - "phonenumbers<9.0.0,>=8.13.52", - "reportlab<5.0.0,>=4.2.5", + "phonenumbers>=9.0.0,<10.0.0", + "reportlab<5.0.0,>=4.3.1", "django-haystack<4.0.0,>=3.3.0", "xapian-haystack<4.0.0,>=3.1.0", "libsass<1.0.0,>=0.23.0", "django-ordered-model<4.0.0,>=3.7.4", - "django-simple-captcha<1.0.0,>=0.6.0", + "django-simple-captcha<1.0.0,>=0.6.2", "python-dateutil<3.0.0.0,>=2.9.0.post0", - "sentry-sdk<3.0.0,>=2.19.2", - "Jinja2<4.0.0,>=3.1.4", + "sentry-sdk<3.0.0,>=2.22.0", + "jinja2<4.0.0,>=3.1.6", "django-countries<8.0.0,>=7.6.1", "dict2xml<2.0.0,>=1.7.6", "Sphinx<6,>=5", "tomli<3.0.0,>=2.2.1", "django-honeypot<2.0.0,>=1.2.1", - "pydantic-extra-types<3.0.0,>=2.10.1", - "ical<9.0.0,>=8.3.0", + "pydantic-extra-types<3.0.0,>=2.10.2", + "ical<9.0.0,>=8.3.1", "redis[hiredis]<6.0.0,>=5.2.0", "environs[django]<15.0.0,>=14.1.0", "requests>=2.32.3", @@ -61,26 +61,26 @@ prod = [ ] dev = [ "django-debug-toolbar<5.0.0,>=4.4.6", - "ipython<9.0.0,>=8.30.0", - "pre-commit<5.0.0,>=4.0.1", - "ruff<1.0.0,>=0.8.3", + "ipython<10.0.0,>=9.0.2", + "pre-commit<5.0.0,>=4.1.0", + "ruff<1.0.0,>=0.9.10", "djhtml<4.0.0,>=3.0.7", - "faker<34.0.0,>=33.1.0", - "rjsmin<2.0.0,>=1.2.3", + "faker<38.0.0,>=37.0.0", + "rjsmin<2.0.0,>=1.2.4", ] tests = [ "freezegun<2.0.0,>=1.5.1", - "pytest<9.0.0,>=8.3.4", + "pytest<9.0.0,>=8.3.5", "pytest-cov<7.0.0,>=6.0.0", - "pytest-django<5.0.0,>=4.9.0", - "model-bakery<2.0.0,>=1.20.0", + "pytest-django<5.0.0,>=4.10.0", + "model-bakery<2.0.0,>=1.20.4", ] docs = [ "mkdocs<2.0.0,>=1.6.1", - "mkdocs-material<10.0.0,>=9.5.47", - "mkdocstrings<1.0.0,>=0.27.0", - "mkdocstrings-python<2.0.0,>=1.12.2", - "mkdocs-include-markdown-plugin<8.0.0,>=7.1.2", + "mkdocs-material<10.0.0,>=9.6.7", + "mkdocstrings<1.0.0,>=0.28.3", + "mkdocstrings-python<2.0.0,>=1.16.3", + "mkdocs-include-markdown-plugin<8.0.0,>=7.1.5", ] [tool.uv] diff --git a/uv.lock b/uv.lock index f96b078e..a3311096 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.12, <4.0" [[package]] @@ -39,11 +40,24 @@ wheels = [ [[package]] name = "babel" -version = "2.16.0" +version = "2.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "backrefs" +version = "5.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337 }, + { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142 }, + { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021 }, + { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915 }, + { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336 }, ] [[package]] @@ -57,11 +71,11 @@ wheels = [ [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, ] [[package]] @@ -182,80 +196,85 @@ wheels = [ [[package]] name = "coverage" -version = "7.6.10" +version = "7.6.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 }, - { url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 }, - { url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 }, - { url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572 }, - { url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639 }, - { url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072 }, - { url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386 }, - { url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054 }, - { url = "https://files.pythonhosted.org/packages/5c/74/83ae4151c170d8bd071924f212add22a0e62a7fe2b149edf016aeecad17c/coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359", size = 210904 }, - { url = "https://files.pythonhosted.org/packages/c3/54/de0893186a221478f5880283119fc40483bc460b27c4c71d1b8bba3474b9/coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247", size = 211692 }, - { url = "https://files.pythonhosted.org/packages/25/6d/31883d78865529257bf847df5789e2ae80e99de8a460c3453dbfbe0db069/coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9", size = 208308 }, - { url = "https://files.pythonhosted.org/packages/70/22/3f2b129cc08de00c83b0ad6252e034320946abfc3e4235c009e57cfeee05/coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b", size = 208565 }, - { url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083 }, - { url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235 }, - { url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220 }, - { url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847 }, - { url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922 }, - { url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783 }, - { url = "https://files.pythonhosted.org/packages/3d/f7/64d3298b2baf261cb35466000628706ce20a82d42faf9b771af447cd2b76/coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6", size = 210965 }, - { url = "https://files.pythonhosted.org/packages/d5/58/ec43499a7fc681212fe7742fe90b2bc361cdb72e3181ace1604247a5b24d/coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e", size = 211719 }, - { url = "https://files.pythonhosted.org/packages/ab/c9/f2857a135bcff4330c1e90e7d03446b036b2363d4ad37eb5e3a47bbac8a6/coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe", size = 209050 }, - { url = "https://files.pythonhosted.org/packages/aa/b3/f840e5bd777d8433caa9e4a1eb20503495709f697341ac1a8ee6a3c906ad/coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273", size = 209321 }, - { url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039 }, - { url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758 }, - { url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119 }, - { url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597 }, - { url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473 }, - { url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 }, - { url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 }, - { url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 }, + { url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645 }, + { url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898 }, + { url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987 }, + { url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881 }, + { url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142 }, + { url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437 }, + { url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724 }, + { url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329 }, + { url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289 }, + { url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079 }, + { url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673 }, + { url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945 }, + { url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484 }, + { url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525 }, + { url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179 }, + { url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288 }, + { url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032 }, + { url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315 }, + { url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099 }, + { url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511 }, + { url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729 }, + { url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988 }, + { url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697 }, + { url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033 }, + { url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535 }, + { url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192 }, + { url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627 }, + { url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033 }, + { url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240 }, + { url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 }, ] [[package]] name = "cryptography" -version = "44.0.0" +version = "44.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/4c/45dfa6829acffa344e3967d6006ee4ae8be57af746ae2eba1c431949b32c/cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02", size = 710657 } +sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/09/8cc67f9b84730ad330b3b72cf867150744bf07ff113cda21a15a1c6d2c7c/cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", size = 6541833 }, - { url = "https://files.pythonhosted.org/packages/7e/5b/3759e30a103144e29632e7cb72aec28cedc79e514b2ea8896bb17163c19b/cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092", size = 3922710 }, - { url = "https://files.pythonhosted.org/packages/5f/58/3b14bf39f1a0cfd679e753e8647ada56cddbf5acebffe7db90e184c76168/cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", size = 4137546 }, - { url = "https://files.pythonhosted.org/packages/98/65/13d9e76ca19b0ba5603d71ac8424b5694415b348e719db277b5edc985ff5/cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", size = 3915420 }, - { url = "https://files.pythonhosted.org/packages/b1/07/40fe09ce96b91fc9276a9ad272832ead0fddedcba87f1190372af8e3039c/cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", size = 4154498 }, - { url = "https://files.pythonhosted.org/packages/75/ea/af65619c800ec0a7e4034207aec543acdf248d9bffba0533342d1bd435e1/cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", size = 3932569 }, - { url = "https://files.pythonhosted.org/packages/c7/af/d1deb0c04d59612e3d5e54203159e284d3e7a6921e565bb0eeb6269bdd8a/cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", size = 4016721 }, - { url = "https://files.pythonhosted.org/packages/bd/69/7ca326c55698d0688db867795134bdfac87136b80ef373aaa42b225d6dd5/cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", size = 4240915 }, - { url = "https://files.pythonhosted.org/packages/ef/d4/cae11bf68c0f981e0413906c6dd03ae7fa864347ed5fac40021df1ef467c/cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", size = 2757925 }, - { url = "https://files.pythonhosted.org/packages/64/b1/50d7739254d2002acae64eed4fc43b24ac0cc44bf0a0d388d1ca06ec5bb1/cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", size = 3202055 }, - { url = "https://files.pythonhosted.org/packages/11/18/61e52a3d28fc1514a43b0ac291177acd1b4de00e9301aaf7ef867076ff8a/cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", size = 6542801 }, - { url = "https://files.pythonhosted.org/packages/1a/07/5f165b6c65696ef75601b781a280fc3b33f1e0cd6aa5a92d9fb96c410e97/cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7", size = 3922613 }, - { url = "https://files.pythonhosted.org/packages/28/34/6b3ac1d80fc174812486561cf25194338151780f27e438526f9c64e16869/cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", size = 4137925 }, - { url = "https://files.pythonhosted.org/packages/d0/c7/c656eb08fd22255d21bc3129625ed9cd5ee305f33752ef2278711b3fa98b/cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", size = 3915417 }, - { url = "https://files.pythonhosted.org/packages/ef/82/72403624f197af0db6bac4e58153bc9ac0e6020e57234115db9596eee85d/cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", size = 4155160 }, - { url = "https://files.pythonhosted.org/packages/a2/cd/2f3c440913d4329ade49b146d74f2e9766422e1732613f57097fea61f344/cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", size = 3932331 }, - { url = "https://files.pythonhosted.org/packages/7f/df/8be88797f0a1cca6e255189a57bb49237402b1880d6e8721690c5603ac23/cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", size = 4017372 }, - { url = "https://files.pythonhosted.org/packages/af/36/5ccc376f025a834e72b8e52e18746b927f34e4520487098e283a719c205e/cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", size = 4239657 }, - { url = "https://files.pythonhosted.org/packages/46/b0/f4f7d0d0bcfbc8dd6296c1449be326d04217c57afb8b2594f017eed95533/cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", size = 2758672 }, - { url = "https://files.pythonhosted.org/packages/97/9b/443270b9210f13f6ef240eff73fd32e02d381e7103969dc66ce8e89ee901/cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", size = 3202071 }, + { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 }, + { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 }, + { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 }, + { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 }, + { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 }, + { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 }, + { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 }, + { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 }, + { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 }, + { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 }, + { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 }, + { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 }, + { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 }, + { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 }, + { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 }, + { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 }, + { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 }, + { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 }, + { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 }, + { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 }, + { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 }, + { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 }, + { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 }, + { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 }, ] [[package]] name = "decorator" -version = "5.1.1" +version = "5.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, ] [[package]] @@ -300,16 +319,16 @@ wheels = [ [[package]] name = "django" -version = "4.2.17" +version = "4.2.20" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/58/709978ddf7e9393c0a89b57a5edbd764ee76eeea68697af3f77f3820980b/Django-4.2.17.tar.gz", hash = "sha256:6b56d834cc94c8b21a8f4e775064896be3b4a4ca387f2612d4406a5927cd2fdc", size = 10437674 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/dd/33d2a11713f6b78493273a32d99bb449f2d93663ed4ec15a8b890d44ba04/Django-4.2.20.tar.gz", hash = "sha256:92bac5b4432a64532abb73b2ac27203f485e40225d2640a7fbef2b62b876e789", size = 10432686 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/85/457360cb3de496382e35db4c2af054066df5c40e26df31400d0109a0500c/Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0", size = 7993390 }, + { url = "https://files.pythonhosted.org/packages/b3/5d/7571ba1c288ead056dda7adad46b25cbf64790576f095565282e996138b1/Django-4.2.20-py3-none-any.whl", hash = "sha256:213381b6e4405f5c8703fffc29cd719efdf189dec60c67c04f76272b3dc845b9", size = 7993584 }, ] [[package]] @@ -397,7 +416,7 @@ wheels = [ [[package]] name = "django-ninja-extra" -version = "0.22.0" +version = "0.22.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, @@ -406,9 +425,9 @@ dependencies = [ { name = "django-ninja" }, { name = "injector" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/03/c498bde769fdb821b8819e21c2953329d7e16b5cb49cbec182328f3143ea/django_ninja_extra-0.22.0.tar.gz", hash = "sha256:a4ecc91b6dce456549754b5c1ac405c7fec3a0b3f03b98dfbe9dc030e5b82ba9", size = 48372 } +sdist = { url = "https://files.pythonhosted.org/packages/87/d6/ecd8b14a8e182226fd74b90901fb693ca09e5aa9ddd7b46bf035e175c47e/django_ninja_extra-0.22.4.tar.gz", hash = "sha256:55a99edd8134558a67d5c5e5ee6ceaf098d79dc4aa3fb2adad0f0948687bc6c5", size = 53280 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8a/67af898af0bdc3269c682bb1b2f553ddfde313e95f666e07b61ff0990f7a/django_ninja_extra-0.22.0-py3-none-any.whl", hash = "sha256:7f304901e71900f2921c0809d1541408536ad985e4535ee01ed97a1fd312b4aa", size = 65911 }, + { url = "https://files.pythonhosted.org/packages/00/4f/c15e24973d7569a214c0f70c1698ac5dcfa60b8f0ddc5967bef6fb4b6e67/django_ninja_extra-0.22.4-py3-none-any.whl", hash = "sha256:d57b5f75754bfa900da5f597e2365215fac48b62f88d28e7aebab07cd360f52d", size = 72788 }, ] [[package]] @@ -443,16 +462,16 @@ sdist = { url = "https://files.pythonhosted.org/packages/70/e3/9372fcdca8e9c3205 [[package]] name = "django-simple-captcha" -version = "0.6.0" +version = "0.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, { name = "django-ranged-response" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/66/e9dbc3a1fe6569b29d8835beed94faf058ab8263154929f5fb5cd26f43aa/django-simple-captcha-0.6.0.tar.gz", hash = "sha256:d188516d326fadd2d5ad076eb89649d55c02cabafe3fdcc2154ac18e9f6d4b97", size = 120602 } +sdist = { url = "https://files.pythonhosted.org/packages/3a/d5/1dd445fc1a648045044b80be224ca6b2430ffd62dc139e45b2d1b0184cc4/django_simple_captcha-0.6.2.tar.gz", hash = "sha256:24db2bd1386c1833618465862e3518d310246d88f5e7f3bec23a5bda768ef834", size = 123994 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/fa/83160be2a72b98ebc04405351cb46a8e74c4468b0bcbdeceece266b94ca7/django_simple_captcha-0.6.0-py2.py3-none-any.whl", hash = "sha256:3ae9a7e650cb0cdbcfd4a75aa91fdf25dcc523ef541a7b1f004bd4357798fc03", size = 92268 }, + { url = "https://files.pythonhosted.org/packages/d7/63/7485d3049c8479f2c77c87aa7557aea300289ad7ac95c9230759f6610c6f/django_simple_captcha-0.6.2-py2.py3-none-any.whl", hash = "sha256:f5b7eee6bfeba6c55b47f2f8414557d5609fc29c4758da2c91ba9a91d6ac349d", size = 93578 }, ] [[package]] @@ -472,15 +491,15 @@ wheels = [ [[package]] name = "environs" -version = "14.1.0" +version = "14.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "marshmallow" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/8f/952bd034eac79c8b68b6c770cb78c2bdcb3140d31ff224847f3520077d75/environs-14.1.0.tar.gz", hash = "sha256:a5f2afe9d5a21b468e74a3cceacf5d2371fd67dbb9a7e54fe62290c75a09cdfa", size = 30985 } +sdist = { url = "https://files.pythonhosted.org/packages/31/d3/e82bdbb8cc332e751f67a3f668c5d134d57f983497d9f3a59a375b6e8fd8/environs-14.1.1.tar.gz", hash = "sha256:03db7ee2d50ec697b68814cd175a3a05a7c7954804e4e419ca8b570dc5a835cf", size = 32050 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/ad/57cfa3e8a006df88e723524127dbab2971a4877c97e7bad070257e15cb6c/environs-14.1.0-py3-none-any.whl", hash = "sha256:a7edda1668ddf1fbfcb7662bdc242dac25648eff2c7fdbaa5d959693afed7a3e", size = 15332 }, + { url = "https://files.pythonhosted.org/packages/f4/1c/ab9752f02d32d981d647c05822be9ff93809be8953dacea2da2bec9a9de9/environs-14.1.1-py3-none-any.whl", hash = "sha256:45bc56f1d53bbc59d8dd69bba97377dd88ec28b8229d81cedbd455b21789445b", size = 15566 }, ] [package.optional-dependencies] @@ -492,33 +511,32 @@ django = [ [[package]] name = "executing" -version = "2.1.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, ] [[package]] name = "faker" -version = "33.3.0" +version = "37.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil" }, - { name = "typing-extensions" }, + { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/d8/8beb1b27c9a068943bc7702418b08f3d52f9f9ac13be16ec1ebc82fa7af2/faker-33.3.0.tar.gz", hash = "sha256:2abb551a05b75d268780b6095100a48afc43c53e97422002efbfc1272ebf5f26", size = 1854655 } +sdist = { url = "https://files.pythonhosted.org/packages/82/c6/6820408cdd87c11f1fbbd2349b05bbda28174d746e6d708ad0f0a934f9d7/faker-37.0.0.tar.gz", hash = "sha256:d2e4e2a30d459a8ec0ae52a552aa51c48973cb32cf51107dee90f58a8322a880", size = 1875487 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/7e/553601891ef96f030a1a6cc14d7957fb1c81e394ca89cafccb97e0eb5882/Faker-33.3.0-py3-none-any.whl", hash = "sha256:ae074d9c7ef65817a93b448141a5531a16b2ea2e563dc5774578197c7c84060c", size = 1894526 }, + { url = "https://files.pythonhosted.org/packages/c6/03/0ffcbc5ab352c266a648d029f79de54ca205c04661203d46a42e3f03492b/faker-37.0.0-py3-none-any.whl", hash = "sha256:2598f78b76710a4ed05e197dda5235be409b4c291ba5c9c7514989cfbc7a5144", size = 1918764 }, ] [[package]] name = "filelock" -version = "3.16.1" +version = "3.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, ] [[package]] @@ -547,14 +565,14 @@ wheels = [ [[package]] name = "griffe" -version = "1.5.4" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/9b/0bc9d53ed6628aae43223dd3c081637da54f66ed17a8c1d9fd36ee5da244/griffe-1.5.4.tar.gz", hash = "sha256:073e78ad3e10c8378c2f798bd4ef87b92d8411e9916e157fd366a17cc4fd4e52", size = 389376 } +sdist = { url = "https://files.pythonhosted.org/packages/a0/1a/d467b93f5e0ea4edf3c1caef44cfdd53a4a498cb3a6bb722df4dd0fdd66a/griffe-1.6.0.tar.gz", hash = "sha256:eb5758088b9c73ad61c7ac014f3cdfb4c57b5c2fcbfca69996584b702aefa354", size = 391819 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/29/d0f156c076ec71eb485e70cbcde4872e3c045cda965a48d1d938aa3d9f76/griffe-1.5.4-py3-none-any.whl", hash = "sha256:ed33af890586a5bebc842fcb919fc694b3dc1bc55b7d9e0228de41ce566b4a1d", size = 128102 }, + { url = "https://files.pythonhosted.org/packages/bf/02/5a22bc98d0aebb68c15ba70d2da1c84a5ef56048d79634e5f96cd2ba96e9/griffe-1.6.0-py3-none-any.whl", hash = "sha256:9f1dfe035d4715a244ed2050dfbceb05b1f470809ed4f6bb10ece5a7302f8dd1", size = 128470 }, ] [[package]] @@ -624,11 +642,11 @@ wheels = [ [[package]] name = "identify" -version = "2.6.5" +version = "2.6.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/92/69934b9ef3c31ca2470980423fda3d00f0460ddefdf30a67adf7f17e2e00/identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc", size = 99213 } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/a71ab060daec766acc30fb47dfca219d03de34a70d616a79a38c6066c5bf/identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf", size = 99249 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/fa/dce098f4cdf7621aa8f7b4f919ce545891f489482f0bfa5102f3eca8608b/identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566", size = 99078 }, + { url = "https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150", size = 99101 }, ] [[package]] @@ -669,11 +687,12 @@ wheels = [ [[package]] name = "ipython" -version = "8.31.0" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "decorator" }, + { name = "ipython-pygments-lexers" }, { name = "jedi" }, { name = "matplotlib-inline" }, { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, @@ -682,9 +701,21 @@ dependencies = [ { name = "stack-data" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/35/6f90fdddff7a08b7b715fccbd2427b5212c9525cd043d26fdc45bee0708d/ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b", size = 5501011 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ce/012a0f40ca58a966f87a6e894d6828e2817657cbdf522b02a5d3a87d92ce/ipython-9.0.2.tar.gz", hash = "sha256:ec7b479e3e5656bf4f58c652c120494df1820f4f28f522fb7ca09e213c2aab52", size = 4366102 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/60/d0feb6b6d9fe4ab89fe8fe5b47cbf6cd936bfd9f1e7ffa9d0015425aeed6/ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6", size = 821583 }, + { url = "https://files.pythonhosted.org/packages/20/3a/917cb9e72f4e1a4ea13c862533205ae1319bd664119189ee5cc9e4e95ebf/ipython-9.0.2-py3-none-any.whl", hash = "sha256:143ef3ea6fb1e1bffb4c74b114051de653ffb7737a3f7ab1670e657ca6ae8c44", size = 600524 }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, ] [[package]] @@ -701,14 +732,14 @@ wheels = [ [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] @@ -773,14 +804,14 @@ wheels = [ [[package]] name = "marshmallow" -version = "3.25.0" +version = "3.26.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/5c/cbfa41491d6c83b36471f2a2f75602349d20a8f88afd94f83c1e68bbc298/marshmallow-3.25.0.tar.gz", hash = "sha256:5ba94a4eb68894ad6761a505eb225daf7e5cb7b4c32af62d4a45e9d42665bc31", size = 176751 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825 } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/26/b347619b719d4c048e038929769f8f6b28c6d930149b40d950bbdde31d48/marshmallow-3.25.0-py3-none-any.whl", hash = "sha256:50894cd57c6b097a6c6ed2bf216af47d10146990a54db52d03e32edb0448c905", size = 49480 }, + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, ] [[package]] @@ -806,11 +837,11 @@ wheels = [ [[package]] name = "mistune" -version = "3.1.0" +version = "3.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/6e/96fc7cb3288666c5de2c396eb0e338dc95f7a8e4920e43e38783a22d0084/mistune-3.1.0.tar.gz", hash = "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667", size = 94401 } +sdist = { url = "https://files.pythonhosted.org/packages/80/f7/f6d06304c61c2a73213c0a4815280f70d985429cda26272f490e42119c1a/mistune-3.1.2.tar.gz", hash = "sha256:733bf018ba007e8b5f2d3a9eb624034f6ee26c4ea769a98ec533ee111d504dff", size = 94613 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/b3/743ffc3f59da380da504d84ccd1faf9a857a1445991ff19bf2ec754163c2/mistune-3.1.0-py3-none-any.whl", hash = "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1", size = 53694 }, + { url = "https://files.pythonhosted.org/packages/12/92/30b4e54c4d7c48c06db61595cffbbf4f19588ea177896f9b78f0fbe021fd/mistune-3.1.2-py3-none-any.whl", hash = "sha256:4b47731332315cdca99e0ded46fc0004001c1299ff773dfb48fbe1fd226de319", size = 53696 }, ] [[package]] @@ -839,16 +870,16 @@ wheels = [ [[package]] name = "mkdocs-autorefs" -version = "1.2.0" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/ae/0f1154c614d6a8b8a36fff084e5b82af3a15f7d2060cf0dcdb1c53297a71/mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f", size = 40262 } +sdist = { url = "https://files.pythonhosted.org/packages/c2/44/140469d87379c02f1e1870315f3143718036a983dd0416650827b8883192/mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079", size = 4131355 } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f", size = 16522 }, + { url = "https://files.pythonhosted.org/packages/f8/29/1125f7b11db63e8e32bcfa0752a4eea30abff3ebd0796f808e14571ddaa2/mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f", size = 5782047 }, ] [[package]] @@ -867,23 +898,24 @@ wheels = [ [[package]] name = "mkdocs-include-markdown-plugin" -version = "7.1.2" +version = "7.1.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mkdocs" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/c6/863c7564872aaebc0d00f3f002adf5ef85f1f5549a44f94aec4c4624c630/mkdocs_include_markdown_plugin-7.1.2.tar.gz", hash = "sha256:1b393157b1aa231b0e6c59ba80f52b723f4b7827bb7a1264b505334f8542aaf1", size = 22213 } +sdist = { url = "https://files.pythonhosted.org/packages/1d/34/ece85384e3ab29f05c2e12e50bde95449aa6dbb47b471923bba8fcf1596c/mkdocs_include_markdown_plugin-7.1.5.tar.gz", hash = "sha256:a986967594da6789226798e3c41c70bc17130fadb92b4313f42bd3defdac0adc", size = 23329 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/3c/41ffab81ef2f1c2186a0fa563680274ad03a651eddf02a1488f69a49524e/mkdocs_include_markdown_plugin-7.1.2-py3-none-any.whl", hash = "sha256:ff1175d1b4f83dea6a38e200d6f0c3db10308975bf60c197d31172671753dbc4", size = 25944 }, + { url = "https://files.pythonhosted.org/packages/ea/eb/472c1bbe93f26fe97647af0b613e9710916a2cf555b64fc969b91e24cf2c/mkdocs_include_markdown_plugin-7.1.5-py3-none-any.whl", hash = "sha256:d0b96edee45e7fda5eb189e63331cfaf1bf1fbdbebbd08371f1daa77045d3ae9", size = 27114 }, ] [[package]] name = "mkdocs-material" -version = "9.5.49" +version = "9.6.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, + { name = "backrefs" }, { name = "colorama" }, { name = "jinja2" }, { name = "markdown" }, @@ -892,12 +924,11 @@ dependencies = [ { name = "paginate" }, { name = "pygments" }, { name = "pymdown-extensions" }, - { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/14/8daeeecee2e25bd84239a843fdcb92b20db88ebbcb26e0d32f414ca54a22/mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d", size = 3949559 } +sdist = { url = "https://files.pythonhosted.org/packages/9b/d7/93e19c9587e5f4ed25647890555d58cf484a4d412be7037dc17b9c9179d9/mkdocs_material-9.6.7.tar.gz", hash = "sha256:3e2c1fceb9410056c2d91f334a00cdea3215c28750e00c691c1e46b2a33309b4", size = 3947458 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/2d/2dd23a36b48421db54f118bb6f6f733dbe2d5c78fe7867375e48649fd3df/mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e", size = 8684098 }, + { url = "https://files.pythonhosted.org/packages/aa/d3/12f22de41bdd9e576ddc459b38c651d68edfb840b32acaa1f46ae36845e3/mkdocs_material-9.6.7-py3-none-any.whl", hash = "sha256:8a159e45e80fcaadd9fbeef62cbf928569b93df954d4dc5ba76d46820caf7b47", size = 8696755 }, ] [[package]] @@ -911,47 +942,46 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "0.27.0" +version = "0.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, { name = "jinja2" }, { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, { name = "mkdocs-autorefs" }, - { name = "platformdirs" }, + { name = "mkdocs-get-deps" }, { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/5a/5de70538c2cefae7ac3a15b5601e306ef3717290cb2aab11d51cbbc2d1c0/mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657", size = 94830 } +sdist = { url = "https://files.pythonhosted.org/packages/e3/48/d134ffefd61349ac96161078d836c7e0c15062e01104327c9a5b23398a0f/mkdocstrings-0.28.3.tar.gz", hash = "sha256:c753516b1b6cee12d00bf9c28255e22c0d71f34c721ca668971fce885d846e0f", size = 104109 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332", size = 30658 }, + { url = "https://files.pythonhosted.org/packages/47/5c/205e4991fad1fbfe78b0d1fcfcf85f55556bcc93a5d6d94c7935e8463b87/mkdocstrings-0.28.3-py3-none-any.whl", hash = "sha256:df5351ffd10477aa3c2ff5cdf17544b936477195436923660274d084a5c1359c", size = 35177 }, ] [[package]] name = "mkdocstrings-python" -version = "1.13.0" +version = "1.16.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/ae/32703e35d74040051c672400fd9f5f2b48a6ea094f5071dd8a0e3be35322/mkdocstrings_python-1.13.0.tar.gz", hash = "sha256:2dbd5757e8375b9720e81db16f52f1856bf59905428fd7ef88005d1370e2f64c", size = 185697 } +sdist = { url = "https://files.pythonhosted.org/packages/d2/b1/dbe9b7cd770b8e6e59e7cd2a409c8180c649653a0078b7658594b6e76db8/mkdocstrings_python-1.16.3.tar.gz", hash = "sha256:d0cdee60399b397e7cbb25075cac4aa7a27b34f75621eb1898dfebc98b88d600", size = 424073 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl", hash = "sha256:b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97", size = 112254 }, + { url = "https://files.pythonhosted.org/packages/ba/58/1b14293589488a7be37c0fc2f2532541962b0853653d554fc42d349b00bd/mkdocstrings_python-1.16.3-py3-none-any.whl", hash = "sha256:17b4d7a1add16032dff4b2f54b51d889fbd5d2fa87c166b22fbc3a56d37ec75e", size = 449301 }, ] [[package]] name = "model-bakery" -version = "1.20.0" +version = "1.20.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/2d/9f61b529c788b367a6bac5b211bf8879036b4bcb27a5e9e78808131ea6fb/model_bakery-1.20.0.tar.gz", hash = "sha256:ec9dc846b9a00b20f92df38fac310263323ab61b59b6eeebf77a4aefb0412724", size = 21016 } +sdist = { url = "https://files.pythonhosted.org/packages/05/dc/6d6260fa30c4d041958f71d6790b722e6f2588fbbca0534779b81a83b66d/model_bakery-1.20.4.tar.gz", hash = "sha256:a0c97e8a27329ecad78136f9d8f573ae392e4282326ea5c5f6daed1173013c4e", size = 21147 } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/f0/c6a6c8ac6fd1dd3c0241ac69230af6592e573d3dedc2ff95b22a352c10f2/model_bakery-1.20.0-py3-none-any.whl", hash = "sha256:875326466f5982ee8f0281abdfa774d78893d5473562575dfd5a9304ac7c5b8c", size = 24019 }, + { url = "https://files.pythonhosted.org/packages/81/23/9b30a9c70e1a6df5100ae8c440b98fc1da6a4ce195327a7fba399569a2ec/model_bakery-1.20.4-py3-none-any.whl", hash = "sha256:30ad372604f326a1ba9f949bad9d0f85e6a510db4ef6a0b07be2d6bd7485008b", size = 24154 }, ] [[package]] @@ -1013,11 +1043,11 @@ wheels = [ [[package]] name = "phonenumbers" -version = "8.13.52" +version = "9.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/be/8d1698beaed180df58685a3d0e1aacac86a7b90e74a954ca489bd2a0a247/phonenumbers-8.13.52.tar.gz", hash = "sha256:fdc371ea6a4da052beb1225de63963d5a2fddbbff2bb53e3a957f360e0185f80", size = 2296342 } +sdist = { url = "https://files.pythonhosted.org/packages/88/96/58ab3aa4f8695c85f5dce60c15bb3b113856f420d4f0575f6b6e92c1acb5/phonenumbers-9.0.0.tar.gz", hash = "sha256:094a6f728e3c2b1906df4494a480743a3c797320f721f2b53f1400fd4d8ed5f5", size = 2296775 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/fa/ccc322224bbe27434f67d8fae917affcf6af4a6ef55f82f16ae87d9c784d/phonenumbers-8.13.52-py2.py3-none-any.whl", hash = "sha256:e803210038ece9d208b129e3023dc20e656a820d6bf6f1cb0471d4164f54bada", size = 2582018 }, + { url = "https://files.pythonhosted.org/packages/62/82/943ce12a9db8932be3a1baff7cbc93524262942f2c41678242f410c8f420/phonenumbers-9.0.0-py2.py3-none-any.whl", hash = "sha256:f566eddf6219d9af9b4aad454ba411a1df565d13b875a490fd33d1d202c1148d", size = 2582416 }, ] [[package]] @@ -1078,7 +1108,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.0.1" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -1087,21 +1117,21 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } +sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, + { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, ] [[package]] name = "prompt-toolkit" -version = "3.0.48" +version = "3.0.50" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, ] [[package]] @@ -1121,15 +1151,15 @@ wheels = [ [[package]] name = "psycopg" -version = "3.2.3" +version = "3.2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/ad/7ce016ae63e231575df0498d2395d15f005f05e32d3a2d439038e1bd0851/psycopg-3.2.3.tar.gz", hash = "sha256:a5764f67c27bec8bfac85764d23c534af2c27b893550377e37ce59c12aac47a2", size = 155550 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/cf/dc1a4d45e3c6222fe272a245c5cea9a969a7157639da606ac7f2ab5de3a1/psycopg-3.2.5.tar.gz", hash = "sha256:f5f750611c67cb200e85b408882f29265c66d1de7f813add4f8125978bfd70e8", size = 156158 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/21/534b8f5bd9734b7a2fcd3a16b1ee82ef6cad81a4796e95ebf4e0c6a24119/psycopg-3.2.3-py3-none-any.whl", hash = "sha256:644d3973fe26908c73d4be746074f6e5224b03c1101d302d9a53bf565ad64907", size = 197934 }, + { url = "https://files.pythonhosted.org/packages/18/f3/14a1370b1449ca875d5e353ef02cb9db6b70bd46ec361c236176837c0be1/psycopg-3.2.5-py3-none-any.whl", hash = "sha256:b782130983e5b3de30b4c529623d3687033b4dafa05bb661fc6bf45837ca5879", size = 198749 }, ] [package.optional-dependencies] @@ -1139,9 +1169,9 @@ c = [ [[package]] name = "psycopg-c" -version = "3.2.3" +version = "3.2.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/ba/74caf4eab78d95a173e65cb81507a589365aeafb1d9c84f374002b51dc53/psycopg_c-3.2.3.tar.gz", hash = "sha256:06ae7db8eaec1a3845960fa7f997f4ccdb1a7a7ab8dc593a680bcc74e1359671", size = 598888 } +sdist = { url = "https://files.pythonhosted.org/packages/cf/cb/468dcca82f08b47af59af4681ef39473cf5c0ef2e09775c701ccdf7284d6/psycopg_c-3.2.5.tar.gz", hash = "sha256:57ad4cfd28de278c424aaceb1f2ad5c7910466e315dfe84e403f3c7a0a2ce81b", size = 609318 } [[package]] name = "ptyprocess" @@ -1172,16 +1202,16 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 }, + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, ] [[package]] @@ -1225,37 +1255,37 @@ wheels = [ [[package]] name = "pydantic-extra-types" -version = "2.10.1" +version = "2.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/12/844a4796dbbc814ef0a706f403cb0f3029bf324c2bca2bf0681f4f7d8618/pydantic_extra_types-2.10.1.tar.gz", hash = "sha256:e4f937af34a754b8f1fa228a2fac867091a51f56ed0e8a61d5b3a6719b13c923", size = 85694 } +sdist = { url = "https://files.pythonhosted.org/packages/23/ed/69f3f3de12c02ebd58b2f66ffb73d0f5a1b10b322227897499753cebe818/pydantic_extra_types-2.10.2.tar.gz", hash = "sha256:934d59ab7a02ff788759c3a97bc896f5cfdc91e62e4f88ea4669067a73f14b98", size = 86893 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/f1/92f7b4711d3d0d08981c2677ec9cdde6cb114205a69814bf803e0be5ae9b/pydantic_extra_types-2.10.1-py3-none-any.whl", hash = "sha256:db2c86c04a837bbac0d2d79bbd6f5d46c4c9253db11ca3fdd36a2b282575f1e2", size = 35155 }, + { url = "https://files.pythonhosted.org/packages/08/da/86bc9addde8a24348ac15f8f7dcb853f78e9573c7667800dd9bc60558678/pydantic_extra_types-2.10.2-py3-none-any.whl", hash = "sha256:9eccd55a2b7935cea25f0a67f6ff763d55d80c41d86b887d88915412ccf5b7fa", size = 35473 }, ] [[package]] name = "pygments" -version = "2.19.0" +version = "2.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/c0/9c9832e5be227c40e1ce774d493065f83a91d6430baa7e372094e9683a45/pygments-2.19.0.tar.gz", hash = "sha256:afc4146269910d4bdfabcd27c24923137a74d562a23a320a41a55ad303e19783", size = 4967733 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/dc/fde3e7ac4d279a331676829af4afafd113b34272393d73f610e8f0329221/pygments-2.19.0-py3-none-any.whl", hash = "sha256:4755e6e64d22161d5b61432c0600c923c5927214e7c956e31c23923c89251a9b", size = 1225305 }, + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, ] [[package]] name = "pymdown-extensions" -version = "10.13" +version = "10.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/87/4998d1aac5afea5b081238a609d9814f4c33cd5c7123503276d1105fb6a9/pymdown_extensions-10.13.tar.gz", hash = "sha256:e0b351494dc0d8d14a1f52b39b1499a00ef1566b4ba23dc74f1eba75c736f5dd", size = 843302 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846 } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/7f/46c7122186759350cf523c71d29712be534f769f073a1d980ce8f095072c/pymdown_extensions-10.13-py3-none-any.whl", hash = "sha256:80bc33d715eec68e683e04298946d47d78c7739e79d808203df278ee8ef89428", size = 264108 }, + { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467 }, ] [[package]] @@ -1269,7 +1299,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.4" +version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1277,9 +1307,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] [[package]] @@ -1297,14 +1327,14 @@ wheels = [ [[package]] name = "pytest-django" -version = "4.9.0" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/c0/43c8b2528c24d7f1a48a47e3f7381f5ab2ae8c64634b0c3f4bd843063955/pytest_django-4.9.0.tar.gz", hash = "sha256:8bf7bc358c9ae6f6fc51b6cebb190fe20212196e6807121f11bd6a3b03428314", size = 84067 } +sdist = { url = "https://files.pythonhosted.org/packages/a5/10/a096573b4b896f18a8390d9dafaffc054c1f613c60bf838300732e538890/pytest_django-4.10.0.tar.gz", hash = "sha256:1091b20ea1491fd04a310fc9aaff4c01b4e8450e3b157687625e16a6b5f3a366", size = 84710 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/fe/54f387ee1b41c9ad59e48fb8368a361fad0600fe404315e31a12bacaea7d/pytest_django-4.9.0-py3-none-any.whl", hash = "sha256:1d83692cb39188682dbb419ff0393867e9904094a549a7d38a3154d5731b2b99", size = 23723 }, + { url = "https://files.pythonhosted.org/packages/58/4c/a4fe18205926216e1aebe1f125cba5bce444f91b6e4de4f49fa87e322775/pytest_django-4.10.0-py3-none-any.whl", hash = "sha256:57c74ef3aa9d89cae5a5d73fbb69a720a62673ade7ff13b9491872409a3f5918", size = 23975 }, ] [[package]] @@ -1380,55 +1410,17 @@ hiredis = [ { name = "hiredis" }, ] -[[package]] -name = "regex" -version = "2024.11.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, - { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, - { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, - { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, - { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, - { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, - { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, - { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, - { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, - { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, - { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, - { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, - { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, - { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, - { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, - { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, - { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, - { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, - { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, - { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, - { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, - { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, - { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, - { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, - { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, - { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, - { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, - { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, - { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, - { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, -] - [[package]] name = "reportlab" -version = "4.2.5" +version = "4.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "chardet" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/4c/ac8c34dc022fd4f542bc86d266b0bf83ce079917875d16ab52b95c588a34/reportlab-4.2.5.tar.gz", hash = "sha256:5cf35b8fd609b68080ac7bbb0ae1e376104f7d5f7b2d3914c7adc63f2593941f", size = 3581379 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/5c/9b23c8a9a69f2bc1f1268ed545f393a60b59cbe5f9d861a28b676f809729/reportlab-4.3.1.tar.gz", hash = "sha256:230f78b21667194d8490ac9d12958d5c14686352db7fbe03b95140fafdf5aa97", size = 3499248 } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/12/6444906db1bc65d3a8118afb089d53c7eeca0726164f51eb3599de1d0665/reportlab-4.2.5-py3-none-any.whl", hash = "sha256:eb2745525a982d9880babb991619e97ac3f661fae30571b7d50387026ca765ee", size = 1942821 }, + { url = "https://files.pythonhosted.org/packages/ce/6b/42805895ed08a314a01be6110584b5d059328386988ddbc4f8f10014d30e/reportlab-4.3.1-py3-none-any.whl", hash = "sha256:0f37dd16652db3ef84363cf744632a28c38bd480d5bf94683466852d7bb678dd", size = 1949468 }, ] [[package]] @@ -1448,66 +1440,66 @@ wheels = [ [[package]] name = "rjsmin" -version = "1.2.3" +version = "1.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/f5/f6bb9865991d417a9ef6bbbaad28b6e998dbfb3058ac21af92e93d0f05ae/rjsmin-1.2.3.tar.gz", hash = "sha256:1388b52493a4c04fbc970a2d757c301fa05a3c37640314c2ce9dfc8d8a730cc6", size = 417812 } +sdist = { url = "https://files.pythonhosted.org/packages/fb/88/14e888053e1c6baf6a7612fed899571f1f836dcb9575fd8bd89f7b070cbe/rjsmin-1.2.4.tar.gz", hash = "sha256:ffcbe04e0dfac39cea8fbbcb41c38b2e07235ce2188bca15e998da1d348a7860", size = 422289 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/25/70cf303eb6690f0e224b3954faddca1f5d0e39d9c04c03957cd09c8f09f8/rjsmin-1.2.3-cp312-cp312-manylinux1_i686.whl", hash = "sha256:ac911d1a12a6d7879ba52e08c56b0ad1a74377bae52610ea74f0f9d936d41785", size = 31636 }, - { url = "https://files.pythonhosted.org/packages/5c/75/03e4a10d1731ba55be4469270513c213e474ccd7c007090155ed24ef0cb8/rjsmin-1.2.3-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:57a0b2f13402623e4ec44eb7ad8846387b2d5605aa8732a05ebefb2289c24b96", size = 31578 }, - { url = "https://files.pythonhosted.org/packages/6f/cd/ea769787aeeb8052528139742949e6c04c2e643282b4adc1383982593a17/rjsmin-1.2.3-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:e28610cca3ab03e43113eadad4f7dd9ea235ddc29a8dc5462bb161a80e5d251f", size = 31872 }, - { url = "https://files.pythonhosted.org/packages/10/81/7e22bbb9f4344f5c754b06840a094c96b7305ac6ae53f14310e37e7d2d54/rjsmin-1.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d4afb4fc3624dc44a7fbae4e41c0b5dc5d861a7f5de865ad463041ec1b5d835c", size = 35778 }, - { url = "https://files.pythonhosted.org/packages/00/03/eadc12cb23f93ea6efda93eb680230e20ff24a78de4d1c5117f0d9024f88/rjsmin-1.2.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ca26b80c7e63cf0788b41571a4bd08d175df7719364e0dd9a3cf7b6cb1ab834c", size = 35969 }, - { url = "https://files.pythonhosted.org/packages/19/a2/214f2ecc98f8c60d865be97ca3493906b90babd44c041b3fa99df1b4c5df/rjsmin-1.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fcc22001588b84d34bbf2c77afa519894244150c4b0754a6e573298ffac24666", size = 35901 }, - { url = "https://files.pythonhosted.org/packages/32/33/b593980591ff6c35eab62156e200da63224e9acae0dae677868ed89465c2/rjsmin-1.2.3-cp313-cp313-manylinux1_i686.whl", hash = "sha256:624d1a0a35122f3f8955d160a39305cf6f786a5b346ee34c516b391cb153a106", size = 31662 }, - { url = "https://files.pythonhosted.org/packages/17/82/cd03bcd7dd5fa700247b74e4e02c3f19bc4b5e4f4c800de76c63228716bd/rjsmin-1.2.3-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:72bd04b7db6190339d8214a5fd289ca31fc1ed30a240f8b0ca13acb9ce3a88af", size = 31568 }, - { url = "https://files.pythonhosted.org/packages/39/22/506126a7a81e7bbbc8844821797cba6232cef27019c0ebdd6505d6200c6f/rjsmin-1.2.3-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:7559f59f4749519b92d72bb68e33b68463f479a82a2a739f1b28a853067aa0e7", size = 31885 }, - { url = "https://files.pythonhosted.org/packages/9f/bc/d91b2236674f7059114e3924d242d99ea0f2e8f12c7a13759ae6a32d6ab0/rjsmin-1.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:aa8bdecf278f754d1a133ab51119a63a4d38500557912bb0930ae0fd61437ec6", size = 35505 }, - { url = "https://files.pythonhosted.org/packages/f2/13/6bbf91fa75f5c6de814a2d4f59aa1f023a7c4c6723161f1f7dee4768db3d/rjsmin-1.2.3-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:2078acc2d02a005ef122eb330e941462c8c3102cf798ad49f1c5ec18ac714240", size = 35757 }, - { url = "https://files.pythonhosted.org/packages/a0/59/d54aaf0801a3db051ff066cffc18e7317d0f04fb7a947af4591f4d48d9cd/rjsmin-1.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:fa40584fddb4f1d2236119505f6c2fe2b57a1ebaf6eaee2bb2eaac33d2a4ca73", size = 35649 }, - { url = "https://files.pythonhosted.org/packages/b0/d3/48fc1dbd2bd0eefd175589ab6a21538eeec1f905e1cb49d694ffeff00ac4/rjsmin-1.2.3-cp313-cp313t-manylinux1_i686.whl", hash = "sha256:bbe5d8340878b38dd4f7b879ed7728f6fc3d7524ad81a5cfbe4eb8ae63951407", size = 33439 }, - { url = "https://files.pythonhosted.org/packages/aa/23/d56520c757d0602be4d2a7e0eaf1757f33bc56909180e8898f3da011e214/rjsmin-1.2.3-cp313-cp313t-manylinux1_x86_64.whl", hash = "sha256:c298c93f5633cf894325907cf49fc7fb010c0f75dc9cda90b0fc1684ad19e5a3", size = 33279 }, - { url = "https://files.pythonhosted.org/packages/5e/b8/14f40adecdc8fe226ddd21e74b05adfed43cfe7b563266740e73939ad229/rjsmin-1.2.3-cp313-cp313t-manylinux2014_aarch64.whl", hash = "sha256:35f18cffe3f1bf6d96bcfd977199378ebfd641d823b08e235d1e0bb0fbaa5532", size = 33901 }, - { url = "https://files.pythonhosted.org/packages/9f/ca/3950a5ba2618131f6de333a49ae7ef3b6c019e56043a977cc3fe8f6074e9/rjsmin-1.2.3-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9aeadf4dd5f941bebf110fe83960a4bafdac176647537819bb7662f5e9a37aaa", size = 37250 }, - { url = "https://files.pythonhosted.org/packages/b3/d2/a7c6c89b6839dea5d111c615d9103065a21a9b86304713f7856243f077ef/rjsmin-1.2.3-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c3219e6e22897b31c8598cb412ed56bc12a722c1d4f88a71710c16efe8c07d0c", size = 37632 }, - { url = "https://files.pythonhosted.org/packages/a4/1e/78d58249270d3cb5a0be5cea64737203fefa32daf28533d8d704d31726e9/rjsmin-1.2.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:bceccb06b118be890fe735fc09ee256851f4993708cb3647f6c71dd0151cce89", size = 37426 }, + { url = "https://files.pythonhosted.org/packages/29/19/55f2614db12f79f0cfe5fdabe7e3547834e99d6e218915889501abe43599/rjsmin-1.2.4-cp312-cp312-manylinux1_i686.whl", hash = "sha256:eeb69da93df402323b39ecf9e5cf3f767f38114defaae3d51c81d6a5ed98a8ce", size = 31666 }, + { url = "https://files.pythonhosted.org/packages/f1/db/97a18a7c497835afef0efbb59ea39bfdbc9e9168d7c3e6e37305ee96d9e0/rjsmin-1.2.4-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:7f2ee075d0d589c978ea210c234e92161a99ebd6bbf6eebaa25cff5383deda45", size = 31631 }, + { url = "https://files.pythonhosted.org/packages/84/b9/aa8e4bf9d4e840d44a2c54dc0482177afd290c3f1d4508c9a2dee31e8e7b/rjsmin-1.2.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7fd8412349a577104e1ddfd75179fac4906ea22b920f8ae8e2b6e6388dac41ce", size = 31910 }, + { url = "https://files.pythonhosted.org/packages/5e/81/73650e7c2309e70d4fe35f585cc950d33e1d8f5b1eb572099c129a613988/rjsmin-1.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1284364742787ad3deaa2ee0698ff38ad845b81da5e389ba2e232392954fc779", size = 35814 }, + { url = "https://files.pythonhosted.org/packages/52/4b/57e5364c5b2ad7f6e86db5e5e29b6e468f38d2635d6b4e1be9a93124587f/rjsmin-1.2.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1d4ec423f599c248921a9460a970571557a76bc788b1f3366730b57613a42dbb", size = 36019 }, + { url = "https://files.pythonhosted.org/packages/61/49/a7ad3a1564c05fd0f7e74f624ad4539c718c020ada8667e8fee84c45954e/rjsmin-1.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f53817b6b438db0138d808081c9e681b24e151c4693141a32884536b718c9ba0", size = 35933 }, + { url = "https://files.pythonhosted.org/packages/74/b8/01b105f48003deefdf200edc78cfb8d3bd7ab81c6c983257d8cd9ae0c0e7/rjsmin-1.2.4-cp313-cp313-manylinux1_i686.whl", hash = "sha256:c6861f311f110f8499e8341c14e564e97ad42efa6d2063934e6f65afbdb8022d", size = 31704 }, + { url = "https://files.pythonhosted.org/packages/66/36/6b7f813a3b5f0d4e51119752f795119e6b1cf34dff8865ef5c2ebb62a0a3/rjsmin-1.2.4-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:e3c3b02b514b2af93ec6e853617b5935b472dbb9c8642f0a602b6dda60e22639", size = 31604 }, + { url = "https://files.pythonhosted.org/packages/29/86/8179662c5dffdb026f0b25f9a6c491c374cda54fa551700f316d5bce77b2/rjsmin-1.2.4-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:30596e407c2041533c39432e4904ac34b1f449743eb73343b2499302f1a4a205", size = 31919 }, + { url = "https://files.pythonhosted.org/packages/15/ad/b82a5d89fcd9632aff593d2a44199d1e411137173dec8fd60d27a834ffb1/rjsmin-1.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0d2d4659caf8d5579bc86f7c8f62f884af2d1bce23c2696f24926654bcaac247", size = 35553 }, + { url = "https://files.pythonhosted.org/packages/3a/fe/814dbe0b09d51b4543b9518212da447380051d05134e93307e5a6cc94bab/rjsmin-1.2.4-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:bcd1c2070d5e4a2caf4ef90dd8295c6f929b4291708e9a737190ad8b3cb15428", size = 35797 }, + { url = "https://files.pythonhosted.org/packages/4c/64/c948cead4787fa8d3ec065f13fc8eca408da725371dafef19fb94195de85/rjsmin-1.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:fe905de31a4b14c4c9f4fd3e3c44b54de892ba28e2e0e21910bfc35ceefab508", size = 35683 }, + { url = "https://files.pythonhosted.org/packages/2a/3c/b4379d34cee23321484ec99db9981f5a391a12bd24527d364b990aabb78a/rjsmin-1.2.4-cp313-cp313t-manylinux1_i686.whl", hash = "sha256:1942f0385565bf228776ad1cf5292c217427d78ac64490e3651463338ffdec25", size = 33478 }, + { url = "https://files.pythonhosted.org/packages/78/cb/a9adf59e2ff6a472af8aedb6c7ff0e1d0dd866f05f72f6a091be1bfc4b29/rjsmin-1.2.4-cp313-cp313t-manylinux1_x86_64.whl", hash = "sha256:abf76ac0c22773e9237e206c712d16470300455ead582aeda496373b87f757b6", size = 33317 }, + { url = "https://files.pythonhosted.org/packages/30/8f/60453274df3fa8f0ac7e8269dc77a58aa861a940b867790246366713d83a/rjsmin-1.2.4-cp313-cp313t-manylinux2014_aarch64.whl", hash = "sha256:69a0770a2f40dbe93b5ea17efbc6eeba29d8fd83f568eddd03c863701ead7490", size = 33943 }, + { url = "https://files.pythonhosted.org/packages/2b/ef/d7c72daf47113677f1172491825ee5475022da933ceff9b10dcfa6697eb6/rjsmin-1.2.4-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d99626311b8956a482b281477942eab6cb4502c274391b649883cb67954a675c", size = 37297 }, + { url = "https://files.pythonhosted.org/packages/31/05/7912c620deb0f7143f94a0fbd122da53978a8a8eef46f3667cec12488600/rjsmin-1.2.4-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:27c09ad7ed1c2e68b1e0f80544eb4b84d054e8400afed7f0a63c2c4fe8b3be67", size = 37676 }, + { url = "https://files.pythonhosted.org/packages/35/6b/b4e580df3052c58b6a956942f4b5db1c9023fce2d43bd5d58125dab7a0fe/rjsmin-1.2.4-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e6a630f55d2d1f751b8ea4abce43e3dd6971fd3eb1b0b79426beed6c80cb005", size = 37472 }, ] [[package]] name = "ruff" -version = "0.8.6" +version = "0.9.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/00/089db7890ea3be5709e3ece6e46408d6f1e876026ec3fd081ee585fef209/ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5", size = 3473116 } +sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/28/aa07903694637c2fa394a9f4fe93cf861ad8b09f1282fa650ef07ff9fe97/ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3", size = 10628735 }, - { url = "https://files.pythonhosted.org/packages/2b/43/827bb1448f1fcb0fb42e9c6edf8fb067ca8244923bf0ddf12b7bf949065c/ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1", size = 10386758 }, - { url = "https://files.pythonhosted.org/packages/df/93/fc852a81c3cd315b14676db3b8327d2bb2d7508649ad60bfdb966d60738d/ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807", size = 10007808 }, - { url = "https://files.pythonhosted.org/packages/94/e9/e0ed4af1794335fb280c4fac180f2bf40f6a3b859cae93a5a3ada27325ae/ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25", size = 10861031 }, - { url = "https://files.pythonhosted.org/packages/82/68/da0db02f5ecb2ce912c2bef2aa9fcb8915c31e9bc363969cfaaddbc4c1c2/ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d", size = 10388246 }, - { url = "https://files.pythonhosted.org/packages/ac/1d/b85383db181639019b50eb277c2ee48f9f5168f4f7c287376f2b6e2a6dc2/ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75", size = 11424693 }, - { url = "https://files.pythonhosted.org/packages/ac/b7/30bc78a37648d31bfc7ba7105b108cb9091cd925f249aa533038ebc5a96f/ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315", size = 12141921 }, - { url = "https://files.pythonhosted.org/packages/60/b3/ee0a14cf6a1fbd6965b601c88d5625d250b97caf0534181e151504498f86/ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188", size = 11692419 }, - { url = "https://files.pythonhosted.org/packages/ef/d6/c597062b2931ba3e3861e80bd2b147ca12b3370afc3889af46f29209037f/ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf", size = 12981648 }, - { url = "https://files.pythonhosted.org/packages/68/84/21f578c2a4144917985f1f4011171aeff94ab18dfa5303ac632da2f9af36/ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117", size = 11251801 }, - { url = "https://files.pythonhosted.org/packages/6c/aa/1ac02537c8edeb13e0955b5db86b5c050a1dcba54f6d49ab567decaa59c1/ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe", size = 10849857 }, - { url = "https://files.pythonhosted.org/packages/eb/00/020cb222252d833956cb3b07e0e40c9d4b984fbb2dc3923075c8f944497d/ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d", size = 10470852 }, - { url = "https://files.pythonhosted.org/packages/00/56/e6d6578202a0141cd52299fe5acb38b2d873565f4670c7a5373b637cf58d/ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a", size = 10972997 }, - { url = "https://files.pythonhosted.org/packages/be/31/dd0db1f4796bda30dea7592f106f3a67a8f00bcd3a50df889fbac58e2786/ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76", size = 11317760 }, - { url = "https://files.pythonhosted.org/packages/d4/70/cfcb693dc294e034c6fed837fa2ec98b27cc97a26db5d049345364f504bf/ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764", size = 8799729 }, - { url = "https://files.pythonhosted.org/packages/60/22/ae6bcaa0edc83af42751bd193138bfb7598b2990939d3e40494d6c00698c/ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905", size = 9673857 }, - { url = "https://files.pythonhosted.org/packages/91/f8/3765e053acd07baa055c96b2065c7fab91f911b3c076dfea71006666f5b0/ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162", size = 9149556 }, + { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, + { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, + { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, + { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, + { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, + { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, + { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, + { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, + { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, + { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, + { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, + { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, + { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, + { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, + { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, + { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, + { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, ] [[package]] name = "sentry-sdk" -version = "2.19.2" +version = "2.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/4a/eccdcb8c2649d53440ae1902447b86e2e2ad1bc84207c80af9696fa07614/sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d", size = 299047 } +sdist = { url = "https://files.pythonhosted.org/packages/81/b6/662988ecd2345bf6c3a5c306a9a3590852742eff91d0a78a143398b816f3/sentry_sdk-2.22.0.tar.gz", hash = "sha256:b4bf43bb38f547c84b2eadcefbe389b36ef75f3f38253d7a74d6b928c07ae944", size = 303539 } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/4d/74597bb6bcc23abc774b8901277652c61331a9d4d0a8d1bdb20679b9bbcb/sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84", size = 322942 }, + { url = "https://files.pythonhosted.org/packages/12/7f/0e4459173e9671ba5f75a48dda2442bcc48a12c79e54e5789381c8c6a9bc/sentry_sdk-2.22.0-py2.py3-none-any.whl", hash = "sha256:3d791d631a6c97aad4da7074081a57073126c69487560c6f8bffcf586461de66", size = 325815 }, ] [[package]] @@ -1577,33 +1569,33 @@ tests = [ [package.metadata] requires-dist = [ - { name = "cryptography", specifier = ">=44.0.0,<45.0.0" }, + { name = "cryptography", specifier = ">=44.0.2,<45.0.0" }, { name = "dict2xml", specifier = ">=1.7.6,<2.0.0" }, - { name = "django", specifier = ">=4.2.17,<5.0.0" }, + { name = "django", specifier = ">=4.2.20,<5.0.0" }, { name = "django-countries", specifier = ">=7.6.1,<8.0.0" }, { name = "django-haystack", specifier = ">=3.3.0,<4.0.0" }, { name = "django-honeypot", specifier = ">=1.2.1,<2.0.0" }, { name = "django-jinja", specifier = ">=2.11.0,<3.0.0" }, { name = "django-ninja", specifier = ">=1.3.0,<2.0.0" }, - { name = "django-ninja-extra", specifier = ">=0.21.8,<1.0.0" }, + { name = "django-ninja-extra", specifier = ">=0.22.4,<1.0.0" }, { name = "django-ordered-model", specifier = ">=3.7.4,<4.0.0" }, { name = "django-phonenumber-field", specifier = ">=8.0.0,<9.0.0" }, - { name = "django-simple-captcha", specifier = ">=0.6.0,<1.0.0" }, + { name = "django-simple-captcha", specifier = ">=0.6.2,<1.0.0" }, { name = "environs", extras = ["django"], specifier = ">=14.1.0,<15.0.0" }, { name = "honcho", specifier = ">=2.0.0" }, - { name = "ical", specifier = ">=8.3.0,<9.0.0" }, - { name = "jinja2", specifier = ">=3.1.4,<4.0.0" }, + { name = "ical", specifier = ">=8.3.1,<9.0.0" }, + { name = "jinja2", specifier = ">=3.1.6,<4.0.0" }, { name = "libsass", specifier = ">=0.23.0,<1.0.0" }, - { name = "mistune", specifier = ">=3.0.2,<4.0.0" }, - { name = "phonenumbers", specifier = ">=8.13.52,<9.0.0" }, - { name = "pillow", specifier = ">=11.0.0,<12.0.0" }, + { name = "mistune", specifier = ">=3.1.2,<4.0.0" }, + { name = "phonenumbers", specifier = ">=9.0.0,<10.0.0" }, + { name = "pillow", specifier = ">=11.1.0,<12.0.0" }, { name = "psutil", specifier = ">=7.0.0" }, - { name = "pydantic-extra-types", specifier = ">=2.10.1,<3.0.0" }, + { name = "pydantic-extra-types", specifier = ">=2.10.2,<3.0.0" }, { name = "python-dateutil", specifier = ">=2.9.0.post0,<3.0.0.0" }, { name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<6.0.0" }, - { name = "reportlab", specifier = ">=4.2.5,<5.0.0" }, + { name = "reportlab", specifier = ">=4.3.1,<5.0.0" }, { name = "requests", specifier = ">=2.32.3" }, - { name = "sentry-sdk", specifier = ">=2.19.2,<3.0.0" }, + { name = "sentry-sdk", specifier = ">=2.22.0,<3.0.0" }, { name = "sphinx", specifier = ">=5,<6" }, { name = "tomli", specifier = ">=2.2.1,<3.0.0" }, { name = "xapian-haystack", specifier = ">=3.1.0,<4.0.0" }, @@ -1613,26 +1605,26 @@ requires-dist = [ dev = [ { name = "django-debug-toolbar", specifier = ">=4.4.6,<5.0.0" }, { name = "djhtml", specifier = ">=3.0.7,<4.0.0" }, - { name = "faker", specifier = ">=33.1.0,<34.0.0" }, - { name = "ipython", specifier = ">=8.30.0,<9.0.0" }, - { name = "pre-commit", specifier = ">=4.0.1,<5.0.0" }, - { name = "rjsmin", specifier = ">=1.2.3,<2.0.0" }, - { name = "ruff", specifier = ">=0.8.3,<1.0.0" }, + { name = "faker", specifier = ">=37.0.0,<38.0.0" }, + { name = "ipython", specifier = ">=9.0.2,<10.0.0" }, + { name = "pre-commit", specifier = ">=4.1.0,<5.0.0" }, + { name = "rjsmin", specifier = ">=1.2.4,<2.0.0" }, + { name = "ruff", specifier = ">=0.9.10,<1.0.0" }, ] docs = [ { name = "mkdocs", specifier = ">=1.6.1,<2.0.0" }, - { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.2,<8.0.0" }, - { name = "mkdocs-material", specifier = ">=9.5.47,<10.0.0" }, - { name = "mkdocstrings", specifier = ">=0.27.0,<1.0.0" }, - { name = "mkdocstrings-python", specifier = ">=1.12.2,<2.0.0" }, + { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.5,<8.0.0" }, + { name = "mkdocs-material", specifier = ">=9.6.7,<10.0.0" }, + { name = "mkdocstrings", specifier = ">=0.28.3,<1.0.0" }, + { name = "mkdocstrings-python", specifier = ">=1.16.3,<2.0.0" }, ] prod = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" }] tests = [ { name = "freezegun", specifier = ">=1.5.1,<2.0.0" }, - { name = "model-bakery", specifier = ">=1.20.0,<2.0.0" }, - { name = "pytest", specifier = ">=8.3.4,<9.0.0" }, + { name = "model-bakery", specifier = ">=1.20.4,<2.0.0" }, + { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, { name = "pytest-cov", specifier = ">=6.0.0,<7.0.0" }, - { name = "pytest-django", specifier = ">=4.9.0,<5.0.0" }, + { name = "pytest-django", specifier = ">=4.10.0,<5.0.0" }, ] [[package]] @@ -1806,11 +1798,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2024.2" +version = "2025.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } +sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, + { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 }, ] [[package]] @@ -1824,16 +1816,16 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.28.1" +version = "20.29.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/39/689abee4adc85aad2af8174bb195a819d0be064bf55fcc73b49d2b28ae77/virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329", size = 7650532 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/9c/57d19fa093bcf5ac61a48087dd44d00655f85421d1aa9722f8befbf3f40a/virtualenv-20.29.3.tar.gz", hash = "sha256:95e39403fcf3940ac45bc717597dba16110b74506131845d9b687d5e73d947ac", size = 4320280 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/8f/dfb257ca6b4e27cb990f1631142361e4712badab8e3ca8dc134d96111515/virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb", size = 4276719 }, + { url = "https://files.pythonhosted.org/packages/c2/eb/c6db6e3001d58c6a9e67c74bb7b4206767caa3ccc28c6b9eaf4c23fb4e34/virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170", size = 4301458 }, ] [[package]] From bba5339407a09cfe6b4c0bd918e94453dfb45b1e Mon Sep 17 00:00:00 2001 From: imperosol Date: Sun, 9 Mar 2025 15:05:10 +0100 Subject: [PATCH 20/56] apply ruff rule DJ012 --- accounting/models.py | 12 ++++++------ club/tests.py | 2 +- core/models.py | 24 ++++++++++++------------ counter/models.py | 26 +++++++++++++------------- galaxy/models.py | 2 +- trombi/models.py | 4 +--- trombi/views.py | 5 +---- 7 files changed, 35 insertions(+), 40 deletions(-) diff --git a/accounting/models.py b/accounting/models.py index 9b111f61..6fb1a6c8 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -328,6 +328,12 @@ class Operation(models.Model): def __str__(self): return f"{self.amount} € | {self.date} | {self.accounting_type} | {self.done}" + def __getattribute__(self, attr): + if attr == "target": + return self.get_target() + else: + return object.__getattribute__(self, attr) + def save(self, *args, **kwargs): if self.number is None: self.number = self.journal.operations.count() + 1 @@ -337,12 +343,6 @@ class Operation(models.Model): def get_absolute_url(self): return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id}) - def __getattribute__(self, attr): - if attr == "target": - return self.get_target() - else: - return object.__getattribute__(self, attr) - def clean(self): super().clean() if self.date is None: diff --git a/club/tests.py b/club/tests.py index a9b7e2e6..53ac5ff7 100644 --- a/club/tests.py +++ b/club/tests.py @@ -265,7 +265,7 @@ class TestClubModel(TestClub): for membership in memberships.select_related("user"): user = membership.user expected_html += ( - f"" + f'' f"{user.get_display_name()}" f"{settings.SITH_CLUB_ROLES[membership.role]}" f"{membership.description}" diff --git a/core/models.py b/core/models.py index 4748f311..29c622fe 100644 --- a/core/models.py +++ b/core/models.py @@ -1366,6 +1366,18 @@ class PageRev(models.Model): class Meta: ordering = ["date"] + def __getattribute__(self, attr): + if attr == "owner_group": + return self.page.owner_group + elif attr == "edit_groups": + return self.page.edit_groups + elif attr == "view_groups": + return self.page.view_groups + elif attr == "unset_lock": + return self.page.unset_lock + else: + return object.__getattribute__(self, attr) + def __str__(self): return str(self.__dict__) @@ -1379,18 +1391,6 @@ class PageRev(models.Model): def get_absolute_url(self): return reverse("core:page", kwargs={"page_name": self.page._full_name}) - def __getattribute__(self, attr): - if attr == "owner_group": - return self.page.owner_group - elif attr == "edit_groups": - return self.page.edit_groups - elif attr == "view_groups": - return self.page.view_groups - elif attr == "unset_lock": - return self.page.unset_lock - else: - return object.__getattribute__(self, attr) - def can_be_edited_by(self, user): return self.page.can_be_edited_by(user) diff --git a/counter/models.py b/counter/models.py index 1467c9f4..dc043509 100644 --- a/counter/models.py +++ b/counter/models.py @@ -514,11 +514,6 @@ class Counter(models.Model): def __str__(self): return self.name - def get_absolute_url(self) -> str: - if self.type == "EBOUTIC": - return reverse("eboutic:main") - return reverse("counter:details", kwargs={"counter_id": self.id}) - def __getattribute__(self, name: str): if name == "edit_groups": return Group.objects.filter( @@ -526,6 +521,11 @@ class Counter(models.Model): ).all() return object.__getattribute__(self, name) + def get_absolute_url(self) -> str: + if self.type == "EBOUTIC": + return reverse("eboutic:main") + return reverse("counter:details", kwargs={"counter_id": self.id}) + def is_owned_by(self, user: User) -> bool: if user.is_anonymous: return False @@ -1045,14 +1045,6 @@ class CashRegisterSummary(models.Model): def __str__(self): return "At %s by %s - Total: %s €" % (self.counter, self.user, self.get_total()) - def save(self, *args, **kwargs): - if not self.id: - self.date = timezone.now() - return super().save(*args, **kwargs) - - def get_absolute_url(self): - return reverse("counter:cash_summary_list") - def __getattribute__(self, name): if name[:5] == "check": checks = self.items.filter(is_check=True).order_by("value").all() @@ -1089,6 +1081,14 @@ class CashRegisterSummary(models.Model): else: return object.__getattribute__(self, name) + def save(self, *args, **kwargs): + if not self.id: + self.date = timezone.now() + return super().save(*args, **kwargs) + + def get_absolute_url(self): + return reverse("counter:cash_summary_list") + def is_owned_by(self, user): """Method to see if that object can be edited by the given user.""" if user.is_anonymous: diff --git a/galaxy/models.py b/galaxy/models.py index 9316aacf..f306884d 100644 --- a/galaxy/models.py +++ b/galaxy/models.py @@ -525,7 +525,7 @@ class Galaxy(models.Model): self.logger.info( f"Progression: {user1_count}/{rulable_users_count} citizen -- {rulable_users_count - user1_count} remaining" ) - self.logger.info(f"Speed: {60.0*global_avg_speed:.2f} citizen per minute") + self.logger.info(f"Speed: {60.0 * global_avg_speed:.2f} citizen per minute") # We can divide the computed ETA by 2 because each loop, there is one citizen less to check, and maths tell # us that this averages to a division by two diff --git a/trombi/models.py b/trombi/models.py index 214eda4a..8771778b 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -63,9 +63,7 @@ class Trombi(models.Model): comments_deadline = models.DateField( _("comments deadline"), default=date.today, - help_text=_( - "After this date, users won't be " "able to make comments anymore." - ), + help_text=_("After this date, users won't be able to make comments anymore."), ) max_chars = models.IntegerField( _("maximum characters"), diff --git a/trombi/views.py b/trombi/views.py index 7f43e199..513a0d9c 100644 --- a/trombi/views.py +++ b/trombi/views.py @@ -479,10 +479,7 @@ class TrombiCommentFormView(LoginRequiredMixin, View): ) if self.trombi.comments_deadline < date.today(): raise Http404( - _( - "You can not write comment anymore, the deadline is " - "already passed." - ) + _("You can not write comment anymore, the deadline is already passed.") ) return modelform_factory( self.model, From 05edf33062fc430a272aaacfeaf96209bf864a25 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 5 Mar 2025 11:20:27 +0100 Subject: [PATCH 21/56] Compile openapi client in background when django runserver is reloading --- package.json | 1 + .../management/commands/collectstatic.py | 8 +++++++- staticfiles/management/commands/runserver.py | 17 ++++++++++++----- staticfiles/processors.py | 6 +++--- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index cc04d56f..dcd79746 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "compile": "vite build --mode production", "compile-dev": "vite build --mode development", "serve": "vite build --mode development --watch --minify false", + "openapi": "openapi-ts", "analyse-dev": "vite-bundle-visualizer --mode development", "analyse-prod": "vite-bundle-visualizer --mode production", "check": "biome check --write" diff --git a/staticfiles/management/commands/collectstatic.py b/staticfiles/management/commands/collectstatic.py index 5b441f6f..f7889087 100644 --- a/staticfiles/management/commands/collectstatic.py +++ b/staticfiles/management/commands/collectstatic.py @@ -50,7 +50,13 @@ class Command(CollectStatic): return Path(location) Scss.compile(self.collect_scss()) - OpenApi.compile() # This needs to be prior to javascript bundling + openapi = OpenApi.compile() # This needs to be prior to javascript bundling + if openapi is not None: + _ = openapi.wait() + if openapi.returncode: + raise RuntimeError( + f"Openapi generation failed with returncode {openapi.returncode}" + ) JSBundler.compile() collected = super().collect() diff --git a/staticfiles/management/commands/runserver.py b/staticfiles/management/commands/runserver.py index 7138dee1..cb44fd60 100644 --- a/staticfiles/management/commands/runserver.py +++ b/staticfiles/management/commands/runserver.py @@ -15,11 +15,18 @@ class Command(Runserver): """Light wrapper around default runserver that integrates javascirpt auto bundling.""" def run(self, **options): - OpenApi.compile() - if ( - os.environ.get(DJANGO_AUTORELOAD_ENV) is None - and settings.PROCFILE_STATIC is not None - ): + is_django_reload = os.environ.get(DJANGO_AUTORELOAD_ENV) is not None + + proc = OpenApi.compile() + # Ensure that the first runserver launch creates openapi files + # before the bundler starts so that it detects them + # When django is reloaded, we can keep this process in background + # to reduce reload time + if proc is not None and not is_django_reload: + _ = proc.wait() + + if not is_django_reload and settings.PROCFILE_STATIC is not None: start_composer(settings.PROCFILE_STATIC) _ = atexit.register(stop_composer, procfile=settings.PROCFILE_STATIC) + super().run(**options) diff --git a/staticfiles/processors.py b/staticfiles/processors.py index bacac363..5f44731b 100644 --- a/staticfiles/processors.py +++ b/staticfiles/processors.py @@ -95,7 +95,7 @@ class JSBundler: def compile(): """Bundle js files with the javascript bundler for production.""" process = subprocess.Popen(["npm", "run", "compile"]) - process.wait() + _ = process.wait() if process.returncode: raise RuntimeError(f"Bundler failed with returncode {process.returncode}") @@ -163,7 +163,7 @@ class OpenApi: OPENAPI_DIR = GENERATED_ROOT / "openapi" @classmethod - def compile(cls): + def compile(cls) -> subprocess.Popen[bytes] | None: """Compile a TS client for the sith API. Only generates it if it changed.""" logging.getLogger("django").info("Compiling open api typescript client") out = cls.OPENAPI_DIR / "schema.json" @@ -191,4 +191,4 @@ class OpenApi: with open(out, "w") as f: _ = f.write(schema) - subprocess.run(["npx", "openapi-ts"], check=True) + return subprocess.Popen(["npm", "run", "openapi"]) From 106dc32a3d2a5eee6d4f0540d3fb888436c91334 Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 9 Mar 2025 16:30:21 +0100 Subject: [PATCH 22/56] Fix schema.json being auto deleted and remove formating and linting of generated openapi client --- openapi-ts.config.ts | 4 +--- package-lock.json | 3 ++- package.json | 2 +- tsconfig.json | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index 32403271..f69aaeb1 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -6,9 +6,7 @@ import { defineConfig } from "@hey-api/openapi-ts"; export default defineConfig({ input: resolve(__dirname, "./staticfiles/generated/openapi/schema.json"), output: { - lint: "biome", - format: "biome", - path: resolve(__dirname, "./staticfiles/generated/openapi"), + path: resolve(__dirname, "./staticfiles/generated/openapi/client"), }, plugins: [ { diff --git a/package-lock.json b/package-lock.json index 4e2852b9..d6d496e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3383,7 +3383,8 @@ "node_modules/country-flag-emoji-polyfill": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/country-flag-emoji-polyfill/-/country-flag-emoji-polyfill-0.1.8.tgz", - "integrity": "sha512-Mbah52sADS3gshUYhK5142gtUuJpHYOXlXtLFI3Ly4RqgkmPMvhX9kMZSTqDM8P7UqtSW99eHKFphhQSGXA3Cg==" + "integrity": "sha512-Mbah52sADS3gshUYhK5142gtUuJpHYOXlXtLFI3Ly4RqgkmPMvhX9kMZSTqDM8P7UqtSW99eHKFphhQSGXA3Cg==", + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", diff --git a/package.json b/package.json index dcd79746..83b65145 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "license": "GPL-3.0-only", "sideEffects": [".css"], "imports": { - "#openapi": "./staticfiles/generated/openapi/index.ts", + "#openapi": "./staticfiles/generated/openapi/client/index.ts", "#core:*": "./core/static/bundled/*", "#pedagogy:*": "./pedagogy/static/bundled/*", "#counter:*": "./counter/static/bundled/*", diff --git a/tsconfig.json b/tsconfig.json index a93da92a..e632cc80 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "types": ["jquery", "alpinejs"], "lib": ["es7"], "paths": { - "#openapi": ["./staticfiles/generated/openapi/index.ts"], + "#openapi": ["./staticfiles/generated/openapi/client/index.ts"], "#core:*": ["./core/static/bundled/*"], "#pedagogy:*": ["./pedagogy/static/bundled/*"], "#counter:*": ["./counter/static/bundled/*"], From 7c3186da79f96e68624f28b25794923fb07c766d Mon Sep 17 00:00:00 2001 From: imperosol Date: Mon, 10 Mar 2025 10:32:00 +0100 Subject: [PATCH 23/56] apply ruff rule A005 --- accounting/views.py | 6 +++--- accounting/widgets/{select.py => ajax_select.py} | 5 ++++- club/forms.py | 2 +- club/widgets/{select.py => ajax_select.py} | 5 ++++- com/api.py | 2 +- com/forms.py | 2 +- com/{calendar.py => ics_calendar.py} | 0 com/signals.py | 2 +- com/tests/test_api.py | 2 +- com/tests/test_views.py | 4 ++-- com/views.py | 2 +- core/management/commands/populate.py | 2 +- core/views/__init__.py | 2 +- core/views/files.py | 2 +- core/views/forms.py | 2 +- core/views/group.py | 2 +- core/views/{site.py => index.py} | 0 core/views/widgets/{select.py => ajax_select.py} | 0 counter/forms.py | 6 +++--- counter/widgets/{select.py => ajax_select.py} | 5 ++++- election/views.py | 4 ++-- forum/views.py | 6 +++--- rootplace/forms.py | 2 +- sas/forms.py | 4 ++-- sas/widgets/{select.py => ajax_select.py} | 5 ++++- subscription/forms.py | 2 +- trombi/views.py | 2 +- 27 files changed, 45 insertions(+), 33 deletions(-) rename accounting/widgets/{select.py => ajax_select.py} (90%) rename club/widgets/{select.py => ajax_select.py} (83%) rename com/{calendar.py => ics_calendar.py} (100%) rename core/views/{site.py => index.py} (100%) rename core/views/widgets/{select.py => ajax_select.py} (100%) rename counter/widgets/{select.py => ajax_select.py} (93%) rename sas/widgets/{select.py => ajax_select.py} (83%) diff --git a/accounting/views.py b/accounting/views.py index f9fd8412..12128a04 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -39,12 +39,12 @@ from accounting.models import ( Operation, SimplifiedAccountingType, ) -from accounting.widgets.select import ( +from accounting.widgets.ajax_select import ( AutoCompleteSelectClubAccount, AutoCompleteSelectCompany, ) from club.models import Club -from club.widgets.select import AutoCompleteSelectClub +from club.widgets.ajax_select import AutoCompleteSelectClub from core.auth.mixins import ( CanCreateMixin, CanEditMixin, @@ -54,7 +54,7 @@ from core.auth.mixins import ( from core.models import User from core.views.forms import SelectDate, SelectFile from core.views.mixins import TabedViewMixin -from core.views.widgets.select import AutoCompleteSelectUser +from core.views.widgets.ajax_select import AutoCompleteSelectUser from counter.models import Counter, Product, Selling # Main accounting view diff --git a/accounting/widgets/select.py b/accounting/widgets/ajax_select.py similarity index 90% rename from accounting/widgets/select.py rename to accounting/widgets/ajax_select.py index 6b3145b7..76bd8382 100644 --- a/accounting/widgets/select.py +++ b/accounting/widgets/ajax_select.py @@ -2,7 +2,10 @@ from pydantic import TypeAdapter from accounting.models import ClubAccount, Company from accounting.schemas import ClubAccountSchema, CompanySchema -from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple +from core.views.widgets.ajax_select import ( + AutoCompleteSelect, + AutoCompleteSelectMultiple, +) _js = ["bundled/accounting/components/ajax-select-index.ts"] diff --git a/club/forms.py b/club/forms.py index 76d13e2c..e63627eb 100644 --- a/club/forms.py +++ b/club/forms.py @@ -29,7 +29,7 @@ from django.utils.translation import gettext_lazy as _ from club.models import Club, Mailing, MailingSubscription, Membership from core.models import User from core.views.forms import SelectDate, SelectDateTime -from core.views.widgets.select import AutoCompleteSelectMultipleUser +from core.views.widgets.ajax_select import AutoCompleteSelectMultipleUser from counter.models import Counter diff --git a/club/widgets/select.py b/club/widgets/ajax_select.py similarity index 83% rename from club/widgets/select.py rename to club/widgets/ajax_select.py index d46bb344..36ad3e9a 100644 --- a/club/widgets/select.py +++ b/club/widgets/ajax_select.py @@ -2,7 +2,10 @@ from pydantic import TypeAdapter from club.models import Club from club.schemas import ClubSchema -from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple +from core.views.widgets.ajax_select import ( + AutoCompleteSelect, + AutoCompleteSelectMultiple, +) _js = ["bundled/club/components/ajax-select-index.ts"] diff --git a/com/api.py b/com/api.py index 99186f36..6de78a3c 100644 --- a/com/api.py +++ b/com/api.py @@ -9,7 +9,7 @@ from ninja_extra.pagination import PageNumberPaginationExtra from ninja_extra.permissions import IsAuthenticated from ninja_extra.schemas import PaginatedResponseSchema -from com.calendar import IcsCalendar +from com.ics_calendar import IcsCalendar from com.models import News, NewsDate from com.schemas import NewsDateFilterSchema, NewsDateSchema from core.auth.api_permissions import HasPerm diff --git a/com/forms.py b/com/forms.py index 8b81a3f9..e94d697e 100644 --- a/com/forms.py +++ b/com/forms.py @@ -8,7 +8,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from club.models import Club -from club.widgets.select import AutoCompleteSelectClub +from club.widgets.ajax_select import AutoCompleteSelectClub from com.models import News, NewsDate, Poster from core.models import User from core.utils import get_end_of_semester diff --git a/com/calendar.py b/com/ics_calendar.py similarity index 100% rename from com/calendar.py rename to com/ics_calendar.py diff --git a/com/signals.py b/com/signals.py index 1c42c6e9..467159d2 100644 --- a/com/signals.py +++ b/com/signals.py @@ -1,7 +1,7 @@ from django.db.models.signals import post_delete, post_save from django.dispatch import receiver -from com.calendar import IcsCalendar +from com.ics_calendar import IcsCalendar from com.models import News diff --git a/com/tests/test_api.py b/com/tests/test_api.py index ba48f49c..7c3bcb7b 100644 --- a/com/tests/test_api.py +++ b/com/tests/test_api.py @@ -16,7 +16,7 @@ from django.utils.timezone import now from model_bakery import baker, seq from pytest_django.asserts import assertNumQueries -from com.calendar import IcsCalendar +from com.ics_calendar import IcsCalendar from com.models import News, NewsDate from core.markdown import markdown from core.models import User diff --git a/com/tests/test_views.py b/com/tests/test_views.py index ce96766e..03d28adc 100644 --- a/com/tests/test_views.py +++ b/com/tests/test_views.py @@ -305,7 +305,7 @@ class TestNewsCreation(TestCase): # we will just test that the ICS is modified. # Checking that the ICS is *well* modified is up to the ICS tests - with patch("com.calendar.IcsCalendar.make_internal") as mocked: + with patch("com.ics_calendar.IcsCalendar.make_internal") as mocked: self.client.post(reverse("com:news_new"), self.valid_payload) mocked.assert_called() @@ -314,7 +314,7 @@ class TestNewsCreation(TestCase): self.valid_payload["occurrences"] = 2 last_news = News.objects.order_by("id").last() - with patch("com.calendar.IcsCalendar.make_internal") as mocked: + with patch("com.ics_calendar.IcsCalendar.make_internal") as mocked: self.client.post( reverse("com:news_edit", kwargs={"news_id": last_news.id}), self.valid_payload, diff --git a/com/views.py b/com/views.py index e1114e57..f6e12fd2 100644 --- a/com/views.py +++ b/com/views.py @@ -43,8 +43,8 @@ from django.views.generic import DetailView, ListView, TemplateView, View from django.views.generic.edit import CreateView, DeleteView, UpdateView from club.models import Club, Mailing -from com.calendar import IcsCalendar from com.forms import NewsDateForm, NewsForm, PosterForm +from com.ics_calendar import IcsCalendar from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle from core.auth.mixins import ( CanEditPropMixin, diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 21fde2e5..5abcb203 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -46,7 +46,7 @@ from accounting.models import ( SimplifiedAccountingType, ) from club.models import Club, Membership -from com.calendar import IcsCalendar +from com.ics_calendar import IcsCalendar from com.models import News, NewsDate, Sith, Weekmail from core.models import BanGroup, Group, Page, PageRev, SithFile, User from core.utils import resize_image diff --git a/core/views/__init__.py b/core/views/__init__.py index a53671d5..daf84693 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -65,6 +65,6 @@ class DetailFormView(FormView, BaseDetailView): # E402: putting those import at the top of the file would also be difficult from .files import * # noqa: F403 E402 from .group import * # noqa: F403 E402 +from .index import * # noqa: F403 E402 from .page import * # noqa: F403 E402 -from .site import * # noqa: F403 E402 from .user import * # noqa: F403 E402 diff --git a/core/views/files.py b/core/views/files.py index 04498d5c..8070033d 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -41,7 +41,7 @@ from core.auth.mixins import ( ) from core.models import Notification, SithFile, User from core.views.mixins import AllowFragment -from core.views.widgets.select import ( +from core.views.widgets.ajax_select import ( AutoCompleteSelectMultipleGroup, AutoCompleteSelectSithFile, AutoCompleteSelectUser, diff --git a/core/views/forms.py b/core/views/forms.py index 381fc8a3..312d0819 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -50,7 +50,7 @@ from PIL import Image from antispam.forms import AntiSpamEmailField from core.models import Gift, Group, Page, SithFile, User from core.utils import resize_image -from core.views.widgets.select import ( +from core.views.widgets.ajax_select import ( AutoCompleteSelect, AutoCompleteSelectGroup, AutoCompleteSelectMultipleGroup, diff --git a/core/views/group.py b/core/views/group.py index ba6b406d..dac5b395 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -30,7 +30,7 @@ from core.auth.mixins import CanEditMixin from core.models import Group, User from core.views import DetailFormView from core.views.forms import PermissionGroupsForm -from core.views.widgets.select import AutoCompleteSelectMultipleUser +from core.views.widgets.ajax_select import AutoCompleteSelectMultipleUser # Forms diff --git a/core/views/site.py b/core/views/index.py similarity index 100% rename from core/views/site.py rename to core/views/index.py diff --git a/core/views/widgets/select.py b/core/views/widgets/ajax_select.py similarity index 100% rename from core/views/widgets/select.py rename to core/views/widgets/ajax_select.py diff --git a/counter/forms.py b/counter/forms.py index 59762920..b509b515 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -2,9 +2,9 @@ from django import forms from django.utils.translation import gettext_lazy as _ from phonenumber_field.widgets import RegionalPhoneNumberWidget -from club.widgets.select import AutoCompleteSelectClub +from club.widgets.ajax_select import AutoCompleteSelectClub from core.views.forms import NFCTextInput, SelectDate, SelectDateTime -from core.views.widgets.select import ( +from core.views.widgets.ajax_select import ( AutoCompleteSelect, AutoCompleteSelectMultipleGroup, AutoCompleteSelectMultipleUser, @@ -19,7 +19,7 @@ from counter.models import ( Refilling, StudentCard, ) -from counter.widgets.select import ( +from counter.widgets.ajax_select import ( AutoCompleteSelectMultipleCounter, AutoCompleteSelectMultipleProduct, AutoCompleteSelectProduct, diff --git a/counter/widgets/select.py b/counter/widgets/ajax_select.py similarity index 93% rename from counter/widgets/select.py rename to counter/widgets/ajax_select.py index 875c0aca..45888061 100644 --- a/counter/widgets/select.py +++ b/counter/widgets/ajax_select.py @@ -1,6 +1,9 @@ from pydantic import TypeAdapter -from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple +from core.views.widgets.ajax_select import ( + AutoCompleteSelect, + AutoCompleteSelectMultiple, +) from counter.models import Counter, Product, ProductType from counter.schemas import ( ProductTypeSchema, diff --git a/election/views.py b/election/views.py index cd367b63..25866422 100644 --- a/election/views.py +++ b/election/views.py @@ -13,12 +13,12 @@ from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateVi from core.auth.mixins import CanCreateMixin, CanEditMixin, CanViewMixin from core.views.forms import SelectDateTime -from core.views.widgets.markdown import MarkdownInput -from core.views.widgets.select import ( +from core.views.widgets.ajax_select import ( AutoCompleteSelect, AutoCompleteSelectMultipleGroup, AutoCompleteSelectUser, ) +from core.views.widgets.markdown import MarkdownInput from election.models import Candidature, Election, ElectionList, Role, Vote if TYPE_CHECKING: diff --git a/forum/views.py b/forum/views.py index 9501cf1b..3ce1fa68 100644 --- a/forum/views.py +++ b/forum/views.py @@ -42,7 +42,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView from haystack.query import RelatedSearchQuerySet from honeypot.decorators import check_honeypot -from club.widgets.select import AutoCompleteSelectClub +from club.widgets.ajax_select import AutoCompleteSelectClub from core.auth.mixins import ( CanCreateMixin, CanEditMixin, @@ -50,11 +50,11 @@ from core.auth.mixins import ( CanViewMixin, can_view, ) -from core.views.widgets.markdown import MarkdownInput -from core.views.widgets.select import ( +from core.views.widgets.ajax_select import ( AutoCompleteSelect, AutoCompleteSelectMultipleGroup, ) +from core.views.widgets.markdown import MarkdownInput from forum.models import Forum, ForumMessage, ForumMessageMeta, ForumTopic diff --git a/rootplace/forms.py b/rootplace/forms.py index 5e7f8e94..e5796023 100644 --- a/rootplace/forms.py +++ b/rootplace/forms.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from core.models import User, UserBan from core.views.forms import FutureDateTimeField, SelectDateTime -from core.views.widgets.select import AutoCompleteSelectUser +from core.views.widgets.ajax_select import AutoCompleteSelectUser class MergeForm(forms.Form): diff --git a/sas/forms.py b/sas/forms.py index 926fe6ca..d987aaf1 100644 --- a/sas/forms.py +++ b/sas/forms.py @@ -6,9 +6,9 @@ from django.utils.translation import gettext_lazy as _ from core.models import User from core.views import MultipleImageField from core.views.forms import SelectDate -from core.views.widgets.select import AutoCompleteSelectMultipleGroup +from core.views.widgets.ajax_select import AutoCompleteSelectMultipleGroup from sas.models import Album, Picture, PictureModerationRequest -from sas.widgets.select import AutoCompleteSelectAlbum +from sas.widgets.ajax_select import AutoCompleteSelectAlbum class SASForm(forms.Form): diff --git a/sas/widgets/select.py b/sas/widgets/ajax_select.py similarity index 83% rename from sas/widgets/select.py rename to sas/widgets/ajax_select.py index 1d124a27..1650a279 100644 --- a/sas/widgets/select.py +++ b/sas/widgets/ajax_select.py @@ -1,6 +1,9 @@ from pydantic import TypeAdapter -from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple +from core.views.widgets.ajax_select import ( + AutoCompleteSelect, + AutoCompleteSelectMultiple, +) from sas.models import Album from sas.schemas import AlbumSchema diff --git a/subscription/forms.py b/subscription/forms.py index b1d00223..ed9978d4 100644 --- a/subscription/forms.py +++ b/subscription/forms.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _ from core.models import User from core.views.forms import SelectDate, SelectDateTime -from core.views.widgets.select import AutoCompleteSelectUser +from core.views.widgets.ajax_select import AutoCompleteSelectUser from subscription.models import Subscription diff --git a/trombi/views.py b/trombi/views.py index 513a0d9c..f44458ee 100644 --- a/trombi/views.py +++ b/trombi/views.py @@ -47,7 +47,7 @@ from core.auth.mixins import ( from core.models import User from core.views.forms import SelectDate from core.views.mixins import QuickNotifMixin, TabedViewMixin -from core.views.widgets.select import AutoCompleteSelectUser +from core.views.widgets.ajax_select import AutoCompleteSelectUser from trombi.models import Trombi, TrombiClubMembership, TrombiComment, TrombiUser From 650227b6e2c2b8362ae614baf328d8ea9cee85e3 Mon Sep 17 00:00:00 2001 From: imperosol Date: Thu, 20 Feb 2025 15:04:33 +0100 Subject: [PATCH 24/56] typescriptify album-index.js --- sas/static/bundled/sas/album-index.ts | 9 +++------ sas/templates/sas/album.jinja | 8 ++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/sas/static/bundled/sas/album-index.ts b/sas/static/bundled/sas/album-index.ts index ff0976d9..02a744b6 100644 --- a/sas/static/bundled/sas/album-index.ts +++ b/sas/static/bundled/sas/album-index.ts @@ -1,10 +1,6 @@ import { paginated } from "#core:utils/api"; import { History, initialUrlParams, updateQueryString } from "#core:utils/history"; -import { - type PictureSchema, - type PicturesFetchPicturesData, - picturesFetchPictures, -} from "#openapi"; +import { type PictureSchema, type PicturesFetchPicturesData, picturesFetchPictures } from "#openapi"; interface AlbumConfig { albumId: number; @@ -17,12 +13,14 @@ document.addEventListener("alpine:init", () => { page: Number.parseInt(initialUrlParams.get("page")) || 1, pushstate: History.Push /* Used to avoid pushing a state on a back action */, loading: false, + config: config, async init() { await this.fetchPictures(); this.$watch("page", () => { updateQueryString("page", this.page === 1 ? null : this.page, this.pushstate); this.pushstate = History.Push; + this.fetchPictures(); }); window.addEventListener("popstate", () => { @@ -30,7 +28,6 @@ document.addEventListener("alpine:init", () => { this.page = Number.parseInt(new URLSearchParams(window.location.search).get("page")) || 1; }); - this.config = config; }, getPage(page: number) { diff --git a/sas/templates/sas/album.jinja b/sas/templates/sas/album.jinja index 172e81ab..cb81d7b1 100644 --- a/sas/templates/sas/album.jinja +++ b/sas/templates/sas/album.jinja @@ -64,11 +64,7 @@
{% endif %} -
- +
{{ download_button(_("Download album")) }}

{% trans %}Pictures{% endtrans %}

@@ -94,7 +90,7 @@
- {{ paginate_alpine("page", "nbPages()") }} + {{ paginate_alpine("page", "nbPages()") }}
{% if is_sas_admin %} From 218aab1af3103d6054db53228a9c3251c64d9087 Mon Sep 17 00:00:00 2001 From: imperosol Date: Thu, 20 Feb 2025 15:33:05 +0100 Subject: [PATCH 25/56] api to fetch albums --- sas/api.py | 28 +++++++++++++--- sas/schemas.py | 32 +++++++++++++++++++ .../sas/components/ajax-select-index.ts | 6 ++-- sas/tests/test_api.py | 13 ++++++++ sas/widgets/ajax_select.py | 6 ++-- 5 files changed, 74 insertions(+), 11 deletions(-) diff --git a/sas/api.py b/sas/api.py index 11355de5..d9e2ad2e 100644 --- a/sas/api.py +++ b/sas/api.py @@ -1,6 +1,3 @@ -from typing import Annotated - -from annotated_types import MinLen from django.conf import settings from django.db.models import F from django.urls import reverse @@ -16,6 +13,8 @@ from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoo from core.models import Notification, User from sas.models import Album, PeoplePictureRelation, Picture from sas.schemas import ( + AlbumAutocompleteSchema, + AlbumFilterSchema, AlbumSchema, IdentifiedUserSchema, ModerationRequestSchema, @@ -31,11 +30,30 @@ class AlbumController(ControllerBase): @route.get( "/search", response=PaginatedResponseSchema[AlbumSchema], + permissions=[IsAuthenticated], + url_name="search-album", + ) + @paginate(PageNumberPaginationExtra, page_size=50) + def fetch_album(self, filters: Query[AlbumFilterSchema]): + """General-purpose album search.""" + return filters.filter(Album.objects.viewable_by(self.context.request.user)) + + @route.get( + "/autocomplete-search", + response=PaginatedResponseSchema[AlbumAutocompleteSchema], permissions=[CanAccessLookup], ) @paginate(PageNumberPaginationExtra, page_size=50) - def search_album(self, search: Annotated[str, MinLen(1)]): - return Album.objects.filter(name__icontains=search) + def autocomplete_album(self, filters: Query[AlbumFilterSchema]): + """Search route to use exclusively on autocomplete input fields. + + This route is separated from `GET /sas/album/search` because + getting the path of an album may need an absurd amount of db queries. + + If you don't need the path of the albums, + do NOT use this route. + """ + return filters.filter(Album.objects.viewable_by(self.context.request.user)) @api_controller("/sas/picture") diff --git a/sas/schemas.py b/sas/schemas.py index d606219b..76eb908a 100644 --- a/sas/schemas.py +++ b/sas/schemas.py @@ -1,6 +1,8 @@ from datetime import datetime from pathlib import Path +from typing import Annotated +from annotated_types import MinLen from django.urls import reverse from ninja import FilterSchema, ModelSchema, Schema from pydantic import Field, NonNegativeInt @@ -9,7 +11,37 @@ from core.schemas import SimpleUserSchema, UserProfileSchema from sas.models import Album, Picture, PictureModerationRequest +class AlbumFilterSchema(FilterSchema): + search: Annotated[str, MinLen(1)] | None = Field(None, q="name__icontains") + before_date: datetime | None = Field(None, q="event_date__lte") + after_date: datetime | None = Field(None, q="event_date__gte") + parent_id: int | None = Field(None, q="parent_id") + + class AlbumSchema(ModelSchema): + class Meta: + model = Album + fields = ["id", "name", "is_moderated"] + + thumbnail: str | None + sas_url: str + + @staticmethod + def resolve_thumbnail(obj: Album) -> str | None: + # Album thumbnails aren't stored in `Album.thumbnail` but in `Album.file` + # Don't ask me why. + if not obj.file: + return None + return obj.get_download_url() + + @staticmethod + def resolve_sas_url(obj: Album) -> str: + return obj.get_absolute_url() + + +class AlbumAutocompleteSchema(ModelSchema): + """Schema to use on album autocomplete input field.""" + class Meta: model = Album fields = ["id", "name"] diff --git a/sas/static/bundled/sas/components/ajax-select-index.ts b/sas/static/bundled/sas/components/ajax-select-index.ts index 5b811f52..e11d96c2 100644 --- a/sas/static/bundled/sas/components/ajax-select-index.ts +++ b/sas/static/bundled/sas/components/ajax-select-index.ts @@ -2,7 +2,7 @@ import { AjaxSelect } from "#core:core/components/ajax-select-base"; import { registerComponent } from "#core:utils/web-components"; import type { TomOption } from "tom-select/dist/types/types"; import type { escape_html } from "tom-select/dist/types/utils"; -import { type AlbumSchema, albumSearchAlbum } from "#openapi"; +import { type AlbumAutocompleteSchema, albumSearchAlbum } from "#openapi"; @registerComponent("album-ajax-select") export class AlbumAjaxSelect extends AjaxSelect { @@ -18,13 +18,13 @@ export class AlbumAjaxSelect extends AjaxSelect { return []; } - protected renderOption(item: AlbumSchema, sanitize: typeof escape_html) { + protected renderOption(item: AlbumAutocompleteSchema, sanitize: typeof escape_html) { return `
${sanitize(item.path)}
`; } - protected renderItem(item: AlbumSchema, sanitize: typeof escape_html) { + protected renderItem(item: AlbumAutocompleteSchema, sanitize: typeof escape_html) { return `${sanitize(item.path)}`; } } diff --git a/sas/tests/test_api.py b/sas/tests/test_api.py index 9b24688b..25014e86 100644 --- a/sas/tests/test_api.py +++ b/sas/tests/test_api.py @@ -228,3 +228,16 @@ class TestPictureModeration(TestSas): assert res.status_code == 200 assert len(res.json()) == 1 assert res.json()[0]["author"]["id"] == self.user_a.id + + +class TestAlbumSearch(TestSas): + def test_num_queries(self): + """Check the number of queries is stable""" + self.client.force_login(subscriber_user.make()) + cache.clear() + with self.assertNumQueries(7): + # - 2 for authentication + # - 3 to check permissions + # - 1 for pagination + # - 1 for the actual results + self.client.get(reverse("api:search-album")) diff --git a/sas/widgets/ajax_select.py b/sas/widgets/ajax_select.py index 1650a279..d7d15666 100644 --- a/sas/widgets/ajax_select.py +++ b/sas/widgets/ajax_select.py @@ -5,7 +5,7 @@ from core.views.widgets.ajax_select import ( AutoCompleteSelectMultiple, ) from sas.models import Album -from sas.schemas import AlbumSchema +from sas.schemas import AlbumAutocompleteSchema _js = ["bundled/sas/components/ajax-select-index.ts"] @@ -13,7 +13,7 @@ _js = ["bundled/sas/components/ajax-select-index.ts"] class AutoCompleteSelectAlbum(AutoCompleteSelect): component_name = "album-ajax-select" model = Album - adapter = TypeAdapter(list[AlbumSchema]) + adapter = TypeAdapter(list[AlbumAutocompleteSchema]) js = _js @@ -21,6 +21,6 @@ class AutoCompleteSelectAlbum(AutoCompleteSelect): class AutoCompleteSelectMultipleAlbum(AutoCompleteSelectMultiple): component_name = "album-ajax-select" model = Album - adapter = TypeAdapter(list[AlbumSchema]) + adapter = TypeAdapter(list[AlbumAutocompleteSchema]) js = _js From 60db7e251651c9ce43cf2c250a82ad341b39fd91 Mon Sep 17 00:00:00 2001 From: imperosol Date: Fri, 21 Feb 2025 16:44:56 +0100 Subject: [PATCH 26/56] ajaxify album loading in the SAS --- sas/static/bundled/sas/album-index.ts | 35 +++++++++++++-- .../sas/components/ajax-select-index.ts | 4 +- sas/templates/sas/album.jinja | 43 +++++++++++++------ sas/views.py | 4 +- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/sas/static/bundled/sas/album-index.ts b/sas/static/bundled/sas/album-index.ts index 02a744b6..c04df038 100644 --- a/sas/static/bundled/sas/album-index.ts +++ b/sas/static/bundled/sas/album-index.ts @@ -1,14 +1,24 @@ import { paginated } from "#core:utils/api"; import { History, initialUrlParams, updateQueryString } from "#core:utils/history"; -import { type PictureSchema, type PicturesFetchPicturesData, picturesFetchPictures } from "#openapi"; +import { + type AlbumSchema, + type PictureSchema, + type PicturesFetchPicturesData, + albumFetchAlbum, + picturesFetchPictures, +} from "#openapi"; -interface AlbumConfig { +interface AlbumPicturesConfig { albumId: number; maxPageSize: number; } +interface SubAlbumsConfig { + parentId: number; +} + document.addEventListener("alpine:init", () => { - Alpine.data("pictures", (config: AlbumConfig) => ({ + Alpine.data("pictures", (config: AlbumPicturesConfig) => ({ pictures: [] as PictureSchema[], page: Number.parseInt(initialUrlParams.get("page")) || 1, pushstate: History.Push /* Used to avoid pushing a state on a back action */, @@ -52,4 +62,23 @@ document.addEventListener("alpine:init", () => { return Math.ceil(this.pictures.length / config.maxPageSize); }, })); + + Alpine.data("albums", (config: SubAlbumsConfig) => ({ + albums: [] as AlbumSchema[], + config: config, + loading: false, + + async init() { + await this.fetchAlbums(); + }, + + async fetchAlbums() { + this.loading = true; + this.albums = await paginated(albumFetchAlbum, { + // biome-ignore lint/style/useNamingConvention: API is snake_case + query: { parent_id: this.config.parentId }, + }); + this.loading = false; + }, + })); }); diff --git a/sas/static/bundled/sas/components/ajax-select-index.ts b/sas/static/bundled/sas/components/ajax-select-index.ts index e11d96c2..aa640556 100644 --- a/sas/static/bundled/sas/components/ajax-select-index.ts +++ b/sas/static/bundled/sas/components/ajax-select-index.ts @@ -2,7 +2,7 @@ import { AjaxSelect } from "#core:core/components/ajax-select-base"; import { registerComponent } from "#core:utils/web-components"; import type { TomOption } from "tom-select/dist/types/types"; import type { escape_html } from "tom-select/dist/types/utils"; -import { type AlbumAutocompleteSchema, albumSearchAlbum } from "#openapi"; +import { type AlbumAutocompleteSchema, albumAutocompleteAlbum } from "#openapi"; @registerComponent("album-ajax-select") export class AlbumAjaxSelect extends AjaxSelect { @@ -11,7 +11,7 @@ export class AlbumAjaxSelect extends AjaxSelect { protected searchField = ["path", "name"]; protected async search(query: string): Promise { - const resp = await albumSearchAlbum({ query: { search: query } }); + const resp = await albumAutocompleteAlbum({ query: { search: query } }); if (resp.data) { return resp.data.results; } diff --git a/sas/templates/sas/album.jinja b/sas/templates/sas/album.jinja index cb81d7b1..6c2cbcf7 100644 --- a/sas/templates/sas/album.jinja +++ b/sas/templates/sas/album.jinja @@ -53,28 +53,43 @@ {% endif %} {% endif %} - {% if children_albums|length > 0 %} -

{% trans %}Albums{% endtrans %}

-
- {% for a in children_albums %} - {{ display_album(a, is_sas_admin) }} - {% endfor %} + {% if show_albums %} +
+

{% trans %}Albums{% endtrans %}

+
+ +
- -
{% endif %}
- {{ download_button(_("Download album")) }} -

{% trans %}Pictures{% endtrans %}

+
+ {{ download_button(_("Download album")) }}