From 76a64b830a285b7413fd7b54ba8f09da2d71c0ab Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 1 Sep 2025 13:03:03 +0200 Subject: [PATCH 01/85] Updates dockerfile for admin --- admin/Dockerfile | 2 +- pyproject.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/admin/Dockerfile b/admin/Dockerfile index 585a000..324f45e 100644 --- a/admin/Dockerfile +++ b/admin/Dockerfile @@ -5,4 +5,4 @@ WORKDIR /code #RUN bash .docker/scripts/setup-sass.sh COPY ./requirements-dev.txt /code/ RUN pip install -r requirements-dev.txt -COPY . /code/ +#COPY . /code/ diff --git a/pyproject.toml b/pyproject.toml index edaa37c..71b5fec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,10 @@ reportAny = false reportUnknownMemberType = false reportUnknownVariableType = false reportUnknownArgumentType = false +reportUnannotatedClassAttribute = false reportExplicitAny = false +reportImplicitOverride = false +reportUninitializedInstanceVariable = false [tool.ruff] line-length = 100 From 435698f304c42fb7b7a524bb8f9afb732262c243 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 1 Sep 2025 13:03:30 +0200 Subject: [PATCH 02/85] Adds default validity to trustmarktypes --- admin/trustmarks/lib.py | 4 ++-- ...pe_valid_for_alter_trustmarktype_tmtype.py | 23 +++++++++++++++++++ admin/trustmarks/models.py | 3 ++- admin/trustmarks/views.py | 4 +++- 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py diff --git a/admin/trustmarks/lib.py b/admin/trustmarks/lib.py index 749eceb..a0160e6 100644 --- a/admin/trustmarks/lib.py +++ b/admin/trustmarks/lib.py @@ -16,7 +16,7 @@ class TrustMarkTypeRequest(BaseModel): type: str -def add_trustmark(entity: str, trustmarktype: str, r: redis.Redis) -> str: +def add_trustmark(entity: str, trustmarktype: str, expiry: int, r: redis.Redis) -> str: """Adds a new subordinate to the federation. :args entity_id: The entity_id to be added @@ -27,7 +27,7 @@ def add_trustmark(entity: str, trustmarktype: str, r: redis.Redis) -> str: sub_data = {"iss": settings.TRUSTMARK_PROVIDER} sub_data["sub"] = entity now = datetime.now() - exp = now + timedelta(days=365) + exp = now + timedelta(days=expiry) sub_data["iat"] = now.timestamp() sub_data["exp"] = exp.timestamp() # TODO: ref: we have to add this claim too in future diff --git a/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py b/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py new file mode 100644 index 0000000..9c026ad --- /dev/null +++ b/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.4 on 2025-09-01 10:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("trustmarks", "0002_alter_trustmark_unique_together"), + ] + + operations = [ + migrations.AddField( + model_name="trustmarktype", + name="valid_for", + field=models.IntegerField(default=365), + ), + migrations.AlterField( + model_name="trustmarktype", + name="tmtype", + field=models.CharField(unique=True), + ), + ] diff --git a/admin/trustmarks/models.py b/admin/trustmarks/models.py index 1c800e1..ac36b78 100644 --- a/admin/trustmarks/models.py +++ b/admin/trustmarks/models.py @@ -5,7 +5,8 @@ # Create your models here. class TrustMarkType(models.Model): id: int - tmtype = models.CharField() + tmtype = models.CharField(unique=True) + valid_for = models.IntegerField(default=365) # Means by default it is valid for 365 days def __str__(self): return self.tmtype diff --git a/admin/trustmarks/views.py b/admin/trustmarks/views.py index 7e03881..e5b516c 100644 --- a/admin/trustmarks/views.py +++ b/admin/trustmarks/views.py @@ -42,7 +42,9 @@ def addtrustmark(request: HttpRequest) -> HttpResponse: t.save() con: Redis = get_redis_connection("default") # Next create the actual trustmark - _trust_mark = add_trustmark(tmr.entity, trust_mark_type.tmtype, con) + _trust_mark = add_trustmark( + tmr.entity, trust_mark_type.tmtype, trust_mark_type.valid_for, con + ) msg = f"Added {tmr.entity}" except IntegrityError: msg = f"{tmr.entity} was already added for the selected Trust mark." From 6dc98be531a43368ea4598dc211f61cdc5a7a3fa Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 1 Sep 2025 16:02:39 +0200 Subject: [PATCH 03/85] Reformats by ruff --- .../0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py | 1 - 1 file changed, 1 deletion(-) diff --git a/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py b/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py index 9c026ad..d34b63b 100644 --- a/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py +++ b/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("trustmarks", "0002_alter_trustmark_unique_together"), ] From 1311e7d4b05fa35c2d80fea6a1e6ed3fafa77243 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 1 Sep 2025 16:03:04 +0200 Subject: [PATCH 04/85] Adds correct function parameter --- admin/trustmarks/management/commands/reissue_alltms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/trustmarks/management/commands/reissue_alltms.py b/admin/trustmarks/management/commands/reissue_alltms.py index f5d9be6..9a29755 100644 --- a/admin/trustmarks/management/commands/reissue_alltms.py +++ b/admin/trustmarks/management/commands/reissue_alltms.py @@ -12,5 +12,5 @@ def command(): for tm in tms: if tm.active: # Means we can reissue this one - add_trustmark(tm.domain, tm.tmt.tmtype, con) + add_trustmark(tm.domain, tm.tmt.tmtype, tm.tmt.valid_for, con) click.secho(f"Reissued {tm.domain}") From 32f3d2f15c94066dd59e4e2ec4ec836a4f8d7366 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 1 Sep 2025 16:03:22 +0200 Subject: [PATCH 05/85] Adds initial /api/v1 API --- admin/inmoradmin/api.py | 36 ++++++++++++++++++++++++++++++++++++ admin/inmoradmin/urls.py | 2 ++ 2 files changed, 38 insertions(+) create mode 100644 admin/inmoradmin/api.py diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py new file mode 100644 index 0000000..c739824 --- /dev/null +++ b/admin/inmoradmin/api.py @@ -0,0 +1,36 @@ +from django.http import HttpRequest +from ninja import NinjaAPI, Schema +from ninja.pagination import LimitOffsetPagination, paginate +from trustmarks.models import TrustMarkType + +api = NinjaAPI() + + +class TrustMarkTypeSchema(Schema): + tmtype: str + valid_for: int = 365 # How many days the default entry will be valid for + + +@api.post("/trust_mark_type") +def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): + """Creates a new trust_mark_type""" + try: + tmt, created = TrustMarkType.objects.get_or_create( + tmtype=data.tmtype, valid_for=data.valid_for + ) + if created: + return 200, {"msg": "TrustMarkType created Succesfully.", "id": tmt.id} + else: + return 403, {"msg": "TrustMarkType already existed.", "id": tmt.id} + except Exception as e: + print(e) + return 500, {"msg": "Error while creating a new TrustMarkType"} + + +@api.get("/trust_mark_type/list", response=list[TrustMarkTypeSchema]) +@paginate(LimitOffsetPagination) +def list_trust_mark_type( + request: HttpRequest, +): + """Lists all existing TrustMarkType(s) from database.""" + return TrustMarkType.objects.all() diff --git a/admin/inmoradmin/urls.py b/admin/inmoradmin/urls.py index 1b3473b..678c47d 100644 --- a/admin/inmoradmin/urls.py +++ b/admin/inmoradmin/urls.py @@ -19,10 +19,12 @@ from django.urls import include, path from . import views +from .api import api urlpatterns = [ path("trustmarks/", include("trustmarks.urls")), path("entities/", include("entities.urls")), path("admin/", admin.site.urls), + path("api/v1/", api.urls), path("", views.index), ] From 9337182160eb32e2d5855b19e0233a525baece53 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 1 Sep 2025 20:18:30 +0200 Subject: [PATCH 06/85] Fixes error handling in API --- admin/inmoradmin/api.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index c739824..68ea9f4 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -11,7 +11,12 @@ class TrustMarkTypeSchema(Schema): valid_for: int = 365 # How many days the default entry will be valid for -@api.post("/trust_mark_type") +class Message(Schema): + message: str + id: int = 0 + + +@api.post("/trust_mark_type", response={200: Message, 403: Message, 500: Message}) def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): """Creates a new trust_mark_type""" try: @@ -19,12 +24,12 @@ def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): tmtype=data.tmtype, valid_for=data.valid_for ) if created: - return 200, {"msg": "TrustMarkType created Succesfully.", "id": tmt.id} + return {"message": "TrustMarkType created Succesfully.", "id": tmt.id} else: - return 403, {"msg": "TrustMarkType already existed.", "id": tmt.id} + return 403, {"message": f"TrustMarkType already existed.", "id": tmt.id} except Exception as e: print(e) - return 500, {"msg": "Error while creating a new TrustMarkType"} + return 500, {"message": "Error while creating a new TrustMarkType"} @api.get("/trust_mark_type/list", response=list[TrustMarkTypeSchema]) From f409ca84010a9bb9a6881bc5dbec46ab1cf5d309 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Wed, 3 Sep 2025 19:41:06 +0200 Subject: [PATCH 07/85] Adds humanreadable as python dependency --- admin/requirements-dev.txt | 1 + pyproject.toml | 1 + uv.lock | 47 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/admin/requirements-dev.txt b/admin/requirements-dev.txt index 77aecc9..88dcfed 100644 --- a/admin/requirements-dev.txt +++ b/admin/requirements-dev.txt @@ -20,3 +20,4 @@ port-for pytest-redis pytest django-ninja +humanreadable diff --git a/pyproject.toml b/pyproject.toml index 71b5fec..c9c01dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "port-for", "pytest-redis", "pytest", + "humanreadable", ] [project.urls] diff --git a/uv.lock b/uv.lock index 90b6e97..9f6c3b8 100644 --- a/uv.lock +++ b/uv.lock @@ -84,6 +84,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.3" @@ -323,6 +332,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "humanreadable" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typepy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/7a/11b6b14db6f0525ef9052016fa1790fda09fff3b7a24f142213d90113230/humanreadable-0.4.1.tar.gz", hash = "sha256:4350eba873975939dc65bd6a64b1ad885418ff58d9c6693435507382d42a22f3", size = 18494, upload-time = "2025-05-04T05:35:07.231Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/73/e3af126a1399f99e55cc982a486a25173700b3a3e117165767ecd5cab5a8/humanreadable-0.4.1-py3-none-any.whl", hash = "sha256:1341e7b52ab2b884aebc7bc41b2bb4c6bd47b9719598648e11e133e6254aeb4f", size = 11601, upload-time = "2025-05-04T05:35:04.696Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -354,6 +375,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520, upload-time = "2024-03-06T19:58:29.765Z" }, ] +[[package]] +name = "mbstrdecoder" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/ab/05ae008357c8bdb6245ebf8a101d99f26c096e0ea20800b318153da23796/mbstrdecoder-1.1.4.tar.gz", hash = "sha256:8105ef9cf6b7d7d69fe7fd6b68a2d8f281ca9b365d7a9b670be376b2e6c81b21", size = 14527, upload-time = "2025-01-18T10:07:31.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/ac/5ce64a1d4cce00390beab88622a290420401f1cabf05caf2fc0995157c21/mbstrdecoder-1.1.4-py3-none-any.whl", hash = "sha256:03dae4ec50ec0d2ff4743e63fdbd5e0022815857494d35224b60775d3d934a8c", size = 7933, upload-time = "2025-01-18T10:07:29.562Z" }, +] + [[package]] name = "mirakuru" version = "2.6.1" @@ -530,6 +563,7 @@ dependencies = [ { name = "django-types" }, { name = "djangosaml2" }, { name = "httpx" }, + { name = "humanreadable" }, { name = "jwcrypto" }, { name = "port-for" }, { name = "psycopg2-binary" }, @@ -556,6 +590,7 @@ requires-dist = [ { name = "django-types" }, { name = "djangosaml2" }, { name = "httpx" }, + { name = "humanreadable" }, { name = "jwcrypto" }, { name = "port-for" }, { name = "psycopg2-binary" }, @@ -754,6 +789,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/5f/bceb29009670ae6f759340f9cb434121bc5ed84ad0f07bdc6179eaaa3204/ty-0.0.1a19-py3-none-win_arm64.whl", hash = "sha256:893755bb35f30653deb28865707e3b16907375c830546def2741f6ff9a764710", size = 8000810, upload-time = "2025-08-19T13:29:56.796Z" }, ] +[[package]] +name = "typepy" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mbstrdecoder" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/59/4c39942077d7de285f762a91024dbda731be693591732977358f77d120fb/typepy-1.3.4.tar.gz", hash = "sha256:89c1f66de6c6133209c43a94d23431d320ba03ef5db18f241091ea594035d9de", size = 39558, upload-time = "2024-12-29T09:18:15.774Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/31/e393c3830bdedd01735bd195c85ac3034b6bcaf6c18142bab60a4047ca36/typepy-1.3.4-py3-none-any.whl", hash = "sha256:d5ed3e0c7f49521bff0603dd08cf8d453371cf68d65a29d3d0038552ccc46e2e", size = 31449, upload-time = "2024-12-29T09:18:13.135Z" }, +] + [[package]] name = "types-jwcrypto" version = "1.5.0.20250516" From 963b29dd891690fca820f1f156f71eb8a492381a Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Wed, 3 Sep 2025 19:41:51 +0200 Subject: [PATCH 08/85] Fixes format for linting --- admin/inmoradmin/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 68ea9f4..c2c8788 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -26,7 +26,7 @@ def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): if created: return {"message": "TrustMarkType created Succesfully.", "id": tmt.id} else: - return 403, {"message": f"TrustMarkType already existed.", "id": tmt.id} + return 403, {"message": "TrustMarkType already existed.", "id": tmt.id} except Exception as e: print(e) return 500, {"message": "Error while creating a new TrustMarkType"} From 466a28ebd72d48b915f3ed50ecf5b244180735e0 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 06:03:59 +0200 Subject: [PATCH 09/85] Adds router to API --- admin/inmoradmin/api.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index c2c8788..482ab52 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,9 +1,10 @@ from django.http import HttpRequest -from ninja import NinjaAPI, Schema +from ninja import NinjaAPI, Router, Schema from ninja.pagination import LimitOffsetPagination, paginate from trustmarks.models import TrustMarkType api = NinjaAPI() +router = Router() class TrustMarkTypeSchema(Schema): @@ -16,7 +17,7 @@ class Message(Schema): id: int = 0 -@api.post("/trust_mark_type", response={200: Message, 403: Message, 500: Message}) +@router.post("/trust_mark_type", response={200: Message, 403: Message, 500: Message}) def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): """Creates a new trust_mark_type""" try: @@ -32,10 +33,13 @@ def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): return 500, {"message": "Error while creating a new TrustMarkType"} -@api.get("/trust_mark_type/list", response=list[TrustMarkTypeSchema]) +@router.get("/trust_mark_type/list", response=list[TrustMarkTypeSchema]) @paginate(LimitOffsetPagination) def list_trust_mark_type( request: HttpRequest, ): """Lists all existing TrustMarkType(s) from database.""" return TrustMarkType.objects.all() + + +api.add_router("", router) From e14a82b9ff5a3a6fcce3f547eac882d41881d146 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 06:04:30 +0200 Subject: [PATCH 10/85] Adds pytest-django as dependency --- admin/requirements-dev.txt | 1 + pyproject.toml | 3 +++ uv.lock | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/admin/requirements-dev.txt b/admin/requirements-dev.txt index 88dcfed..65f1814 100644 --- a/admin/requirements-dev.txt +++ b/admin/requirements-dev.txt @@ -21,3 +21,4 @@ pytest-redis pytest django-ninja humanreadable +pytest-django diff --git a/pyproject.toml b/pyproject.toml index c9c01dc..3a29eb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "port-for", "pytest-redis", "pytest", + "pytest-django", "humanreadable", ] @@ -89,3 +90,5 @@ exclude = [ [tool.pytest.ini_options] testpaths = "tests" +pythonpath = [".", "admin"] +DJANGO_SETTINGS_MODULE = "inmoradmin.settings" diff --git a/uv.lock b/uv.lock index 9f6c3b8..b4a3eba 100644 --- a/uv.lock +++ b/uv.lock @@ -570,6 +570,7 @@ dependencies = [ { name = "pydantic" }, { name = "pysaml2" }, { name = "pytest" }, + { name = "pytest-django" }, { name = "pytest-redis" }, { name = "pytz" }, { name = "redis" }, @@ -597,6 +598,7 @@ requires-dist = [ { name = "pydantic" }, { name = "pysaml2" }, { name = "pytest" }, + { name = "pytest-django" }, { name = "pytest-redis" }, { name = "pytz" }, { name = "redis" }, @@ -651,6 +653,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] +[[package]] +name = "pytest-django" +version = "4.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" }, +] + [[package]] name = "pytest-redis" version = "3.1.3" From c97da8389cbc08a49b80d2e7385e870033eaf6b0 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 08:52:15 +0200 Subject: [PATCH 11/85] Maps postgesql port to localhost for test running --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 2735438..a5579f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,8 @@ services: start_period: 80s volumes: - ./db:/var/lib/postgresql/data + ports: + - "5432:5432" environment: - 'POSTGRES_HOST_AUTH_METHOD=trust' redis: From 31d221fc75c0908eecded2bc2a3b1abd755da19f Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 09:04:21 +0200 Subject: [PATCH 12/85] Adds minimal python for rust testing pytest and related dependencies for running pytest against TA api. --- pyproject.toml | 27 +- uv.lock | 652 +++++-------------------------------------------- 2 files changed, 69 insertions(+), 610 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3a29eb7..9fdfa70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,30 +10,12 @@ dependencies = [ "jwcrypto", "httpx", "redis", - "django", - "django-types", - "black", - "ruff", - "redis", - "django-extensions", - "django-debug-toolbar", - "django-ninja", - "pytz", - "psycopg2-binary", - "pysaml2", - "djangosaml2", - "django-redis", - "jwcrypto", - "types-jwcrypto", - "httpx", - "django-click", - "pydantic", - "ty", "port-for", "pytest-redis", "pytest", - "pytest-django", - "humanreadable", + "python-dotenv", + "pytest-dotenv", + "ruff", ] [project.urls] @@ -90,5 +72,4 @@ exclude = [ [tool.pytest.ini_options] testpaths = "tests" -pythonpath = [".", "admin"] -DJANGO_SETTINGS_MODULE = "inmoradmin.settings" +pythonpath = ["."] diff --git a/uv.lock b/uv.lock index b4a3eba..1522744 100644 --- a/uv.lock +++ b/uv.lock @@ -2,15 +2,6 @@ version = 1 revision = 3 requires-python = ">=3.13" -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - [[package]] name = "anyio" version = "4.10.0" @@ -24,35 +15,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] -[[package]] -name = "asgiref" -version = "3.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142", size = 36870, upload-time = "2025-07-08T09:07:43.344Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" }, -] - -[[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, -] - [[package]] name = "certifi" version = "2025.8.3" @@ -84,58 +46,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] -[[package]] -name = "chardet" -version = "5.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, - { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, - { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, - { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, - { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, - { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, - { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, -] - -[[package]] -name = "click" -version = "8.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -147,152 +57,37 @@ wheels = [ [[package]] name = "cryptography" -version = "43.0.3" +version = "45.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989, upload-time = "2024-10-18T15:58:32.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303, upload-time = "2024-10-18T15:57:36.753Z" }, - { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905, upload-time = "2024-10-18T15:57:39.166Z" }, - { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271, upload-time = "2024-10-18T15:57:41.227Z" }, - { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606, upload-time = "2024-10-18T15:57:42.903Z" }, - { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484, upload-time = "2024-10-18T15:57:45.434Z" }, - { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131, upload-time = "2024-10-18T15:57:47.267Z" }, - { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647, upload-time = "2024-10-18T15:57:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873, upload-time = "2024-10-18T15:57:51.822Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039, upload-time = "2024-10-18T15:57:54.426Z" }, - { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984, upload-time = "2024-10-18T15:57:56.174Z" }, - { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968, upload-time = "2024-10-18T15:57:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754, upload-time = "2024-10-18T15:58:00.683Z" }, - { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458, upload-time = "2024-10-18T15:58:02.225Z" }, - { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220, upload-time = "2024-10-18T15:58:04.331Z" }, - { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898, upload-time = "2024-10-18T15:58:06.113Z" }, - { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592, upload-time = "2024-10-18T15:58:08.673Z" }, - { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145, upload-time = "2024-10-18T15:58:10.264Z" }, - { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026, upload-time = "2024-10-18T15:58:11.916Z" }, -] - -[[package]] -name = "defusedxml" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, -] - -[[package]] -name = "django" -version = "5.2.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asgiref" }, - { name = "sqlparse" }, - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/9b/779f853c3d2d58b9e08346061ff3e331cdec3fe3f53aae509e256412a593/django-5.2.5.tar.gz", hash = "sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae", size = 10859748, upload-time = "2025-08-06T08:26:29.978Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/6e/98a1d23648e0085bb5825326af17612ecd8fc76be0ce96ea4dc35e17b926/django-5.2.5-py3-none-any.whl", hash = "sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd", size = 8302999, upload-time = "2025-08-06T08:26:23.562Z" }, -] - -[[package]] -name = "django-click" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2e/bb/f47eb64d008805195a5d44a2fb306638a84e673dd5fd4ee738b05fa3a55b/django_click-2.4.1.tar.gz", hash = "sha256:5b989ca791689391e6744f8a4dda9bc0aac06670f408c45794d6b83fbe7577ce", size = 8742, upload-time = "2025-02-27T13:19:28.195Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/f1/3bffa13296a9ee51a266e49db00a7defb64f613d68a80cdb2d50721f4b65/django_click-2.4.1-py2.py3-none-any.whl", hash = "sha256:80cfa57af0fae29a3d47f20a74d71aecd1f0bf299549cddb97d98a949e130f01", size = 7334, upload-time = "2025-02-27T13:19:26.426Z" }, -] - -[[package]] -name = "django-debug-toolbar" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, - { name = "sqlparse" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/d5/5fc90234532088aeec5faa48d5b09951cc7eab6626030ed427d3bd8cd9bc/django_debug_toolbar-6.0.0.tar.gz", hash = "sha256:6eb9fa6f4a5884bf04004700ffb5a44043f1fff38784447fc52c1633448c8c14", size = 305331, upload-time = "2025-07-25T13:11:48.68Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/b5/4724a8c18fcc5b09dca7b7a0e70c34208317bb110075ad12484d6588ae91/django_debug_toolbar-6.0.0-py3-none-any.whl", hash = "sha256:0cf2cac5c307b77d6e143c914e5c6592df53ffe34642d93929e5ef095ae56841", size = 266967, upload-time = "2025-07-25T13:11:47.265Z" }, -] - -[[package]] -name = "django-extensions" -version = "4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/b3/ed0f54ed706ec0b54fd251cc0364a249c6cd6c6ec97f04dc34be5e929eac/django_extensions-4.1.tar.gz", hash = "sha256:7b70a4d28e9b840f44694e3f7feb54f55d495f8b3fa6c5c0e5e12bcb2aa3cdeb", size = 283078, upload-time = "2025-04-11T01:15:39.617Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" }, -] - -[[package]] -name = "django-ninja" -version = "1.4.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/1a/f0d051f54375cfd2310f803925993fdc4172e41e627d63ece48194b07892/django_ninja-1.4.3.tar.gz", hash = "sha256:e46d477ca60c228d2a5eb3cc912094928ea830d364501f966661eeada67cb038", size = 3709571, upload-time = "2025-06-04T15:11:13.408Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/ec/0cfa9b817f048cdec354354ae0569d7c0fd63907e5b1f927a7ee04a18635/django_ninja-1.4.3-py3-none-any.whl", hash = "sha256:f3204137a059437b95677049474220611f1cf9efedba9213556474b75168fa01", size = 2426185, upload-time = "2025-06-04T15:11:11.314Z" }, -] - -[[package]] -name = "django-redis" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, - { name = "redis" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/53/dbcfa1e528e0d6c39947092625b2c89274b5d88f14d357cee53c4d6dbbd4/django_redis-6.0.0.tar.gz", hash = "sha256:2d9cb12a20424a4c4dde082c6122f486628bae2d9c2bee4c0126a4de7fda00dd", size = 56904, upload-time = "2025-06-17T18:15:46.376Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/79/055dfcc508cfe9f439d9f453741188d633efa9eab90fc78a67b0ab50b137/django_redis-6.0.0-py3-none-any.whl", hash = "sha256:20bf0063a8abee567eb5f77f375143c32810c8700c0674ced34737f8de4e36c0", size = 33687, upload-time = "2025-06-17T18:15:34.165Z" }, -] - -[[package]] -name = "django-types" -version = "0.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "types-psycopg2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/26/5f2873f7208dee0791710fda494a8d3ef7fb1d34785069b55168a23e2ac2/django_types-0.22.0.tar.gz", hash = "sha256:4cecc9eee846e7ff2a398bec9dfe6543e76efb922a7a58c5d6064bcb0e6a3dc5", size = 187214, upload-time = "2025-07-15T01:05:48.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3a/0ecbaab07cfe7b0a4fd72dfde4be8e7c11aad2b88c816cfcbb800c14cbda/django_types-0.22.0-py3-none-any.whl", hash = "sha256:ba15c756c7a732e58afd0737e54489f1c5e6f1bd24132e9199c637b1f88b057c", size = 376869, upload-time = "2025-07-15T01:05:46.621Z" }, -] - -[[package]] -name = "djangosaml2" -version = "1.11.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "defusedxml" }, - { name = "django" }, - { name = "pysaml2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/69/90444e1fe4fd65e42768c8ec08d590b6cfdf01a8441bf44cb2005f5117cc/djangosaml2-1.11.1.tar.gz", hash = "sha256:f4fcbba980e6f3ad5b47f1f2d0f7296bcee4e37e2ee8237e44b28efe77f2d383", size = 57721, upload-time = "2025-07-08T09:50:03.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/7a/fabc6a31a328e7a3121828a6911df039faf225bd4ae03fc45f1e689514f0/djangosaml2-1.11.1-py2.py3-none-any.whl", hash = "sha256:9109aaab247cabc7c76ab00d61d34e2813c902538dd7071f7f6bd26e39aadb28", size = 67560, upload-time = "2025-07-08T09:50:01.273Z" }, -] - -[[package]] -name = "elementpath" -version = "4.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/41/afdd82534c80e9675d1c51dc21d0889b72d023bfe395a2f5a44d751d3a73/elementpath-4.8.0.tar.gz", hash = "sha256:5822a2560d99e2633d95f78694c7ff9646adaa187db520da200a8e9479dc46ae", size = 358528, upload-time = "2025-03-03T20:51:08.397Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/95/615af832e7f507fe5ce4562b4be1bd2fec080c4ff6da88dcd0c2dbfca582/elementpath-4.8.0-py3-none-any.whl", hash = "sha256:5393191f84969bcf8033b05ec4593ef940e58622ea13cefe60ecefbbf09d58d9", size = 243271, upload-time = "2025-03-03T20:51:03.027Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a7/35/c495bffc2056f2dadb32434f1feedd79abde2a7f8363e1974afa9c33c7e2/cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", size = 744980, upload-time = "2025-09-01T11:15:03.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/91/925c0ac74362172ae4516000fe877912e33b5983df735ff290c653de4913/cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", size = 7041105, upload-time = "2025-09-01T11:13:59.684Z" }, + { url = "https://files.pythonhosted.org/packages/fc/63/43641c5acce3a6105cf8bd5baeceeb1846bb63067d26dae3e5db59f1513a/cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", size = 4205799, upload-time = "2025-09-01T11:14:02.517Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/c238dd9107f10bfde09a4d1c52fd38828b1aa353ced11f358b5dd2507d24/cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", size = 4430504, upload-time = "2025-09-01T11:14:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/62/62/24203e7cbcc9bd7c94739428cd30680b18ae6b18377ae66075c8e4771b1b/cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", size = 4209542, upload-time = "2025-09-01T11:14:06.309Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e3/e7de4771a08620eef2389b86cd87a2c50326827dea5528feb70595439ce4/cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf", size = 3889244, upload-time = "2025-09-01T11:14:08.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/b8/bca71059e79a0bb2f8e4ec61d9c205fbe97876318566cde3b5092529faa9/cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513", size = 4461975, upload-time = "2025-09-01T11:14:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/58/67/3f5b26937fe1218c40e95ef4ff8d23c8dc05aa950d54200cc7ea5fb58d28/cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3", size = 4209082, upload-time = "2025-09-01T11:14:11.229Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e4/b3e68a4ac363406a56cf7b741eeb80d05284d8c60ee1a55cdc7587e2a553/cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", size = 4460397, upload-time = "2025-09-01T11:14:12.924Z" }, + { url = "https://files.pythonhosted.org/packages/22/49/2c93f3cd4e3efc8cb22b02678c1fad691cff9dd71bb889e030d100acbfe0/cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", size = 4337244, upload-time = "2025-09-01T11:14:14.431Z" }, + { url = "https://files.pythonhosted.org/packages/04/19/030f400de0bccccc09aa262706d90f2ec23d56bc4eb4f4e8268d0ddf3fb8/cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd", size = 4568862, upload-time = "2025-09-01T11:14:16.185Z" }, + { url = "https://files.pythonhosted.org/packages/29/56/3034a3a353efa65116fa20eb3c990a8c9f0d3db4085429040a7eef9ada5f/cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", size = 2936578, upload-time = "2025-09-01T11:14:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/b3/61/0ab90f421c6194705a99d0fa9f6ee2045d916e4455fdbb095a9c2c9a520f/cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", size = 3405400, upload-time = "2025-09-01T11:14:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/63/e8/c436233ddf19c5f15b25ace33979a9dd2e7aa1a59209a0ee8554179f1cc0/cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", size = 7021824, upload-time = "2025-09-01T11:14:20.954Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4c/8f57f2500d0ccd2675c5d0cc462095adf3faa8c52294ba085c036befb901/cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", size = 4202233, upload-time = "2025-09-01T11:14:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ac/59b7790b4ccaed739fc44775ce4645c9b8ce54cbec53edf16c74fd80cb2b/cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", size = 4423075, upload-time = "2025-09-01T11:14:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/d4f07ea21434bf891faa088a6ac15d6d98093a66e75e30ad08e88aa2b9ba/cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", size = 4204517, upload-time = "2025-09-01T11:14:25.679Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/924a723299848b4c741c1059752c7cfe09473b6fd77d2920398fc26bfb53/cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3", size = 3882893, upload-time = "2025-09-01T11:14:27.1Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/4dab2ff0a871cc2d81d3ae6d780991c0192b259c35e4d83fe1de18b20c70/cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1", size = 4450132, upload-time = "2025-09-01T11:14:28.58Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/b2882b65db8fc944585d7fb00d67cf84a9cef4e77d9ba8f69082e911d0de/cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27", size = 4204086, upload-time = "2025-09-01T11:14:30.572Z" }, + { url = "https://files.pythonhosted.org/packages/5d/fa/1d5745d878048699b8eb87c984d4ccc5da4f5008dfd3ad7a94040caca23a/cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", size = 4449383, upload-time = "2025-09-01T11:14:32.046Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/fc61f87931bc030598e1876c45b936867bb72777eac693e905ab89832670/cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", size = 4332186, upload-time = "2025-09-01T11:14:33.95Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/09700ddad7443ccb11d674efdbe9a832b4455dc1f16566d9bd3834922ce5/cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", size = 4561639, upload-time = "2025-09-01T11:14:35.343Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/8f4c1337e9d3b94d8e50ae0b08ad0304a5709d483bfcadfcc77a23dbcb52/cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", size = 2926552, upload-time = "2025-09-01T11:14:36.929Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ff/026513ecad58dacd45d1d24ebe52b852165a26e287177de1d545325c0c25/cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", size = 3392742, upload-time = "2025-09-01T11:14:38.368Z" }, ] [[package]] @@ -332,18 +127,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] -[[package]] -name = "humanreadable" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typepy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/7a/11b6b14db6f0525ef9052016fa1790fda09fff3b7a24f142213d90113230/humanreadable-0.4.1.tar.gz", hash = "sha256:4350eba873975939dc65bd6a64b1ad885418ff58d9c6693435507382d42a22f3", size = 18494, upload-time = "2025-05-04T05:35:07.231Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/73/e3af126a1399f99e55cc982a486a25173700b3a3e117165767ecd5cab5a8/humanreadable-0.4.1-py3-none-any.whl", hash = "sha256:1341e7b52ab2b884aebc7bc41b2bb4c6bd47b9719598648e11e133e6254aeb4f", size = 11601, upload-time = "2025-05-04T05:35:04.696Z" }, -] - [[package]] name = "idna" version = "3.10" @@ -375,18 +158,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520, upload-time = "2024-03-06T19:58:29.765Z" }, ] -[[package]] -name = "mbstrdecoder" -version = "1.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "chardet" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/ab/05ae008357c8bdb6245ebf8a101d99f26c096e0ea20800b318153da23796/mbstrdecoder-1.1.4.tar.gz", hash = "sha256:8105ef9cf6b7d7d69fe7fd6b68a2d8f281ca9b365d7a9b670be376b2e6c81b21", size = 14527, upload-time = "2025-01-18T10:07:31.089Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/ac/5ce64a1d4cce00390beab88622a290420401f1cabf05caf2fc0995157c21/mbstrdecoder-1.1.4-py3-none-any.whl", hash = "sha256:03dae4ec50ec0d2ff4743e63fdbd5e0022815857494d35224b60775d3d934a8c", size = 7933, upload-time = "2025-01-18T10:07:29.562Z" }, -] - [[package]] name = "mirakuru" version = "2.6.1" @@ -399,15 +170,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/ce/139df7074328119869a1041ce91c082d78287541cf867f9c4c85097c5d8b/mirakuru-2.6.1-py3-none-any.whl", hash = "sha256:4be0bfd270744454fa0c0466b8127b66bd55f4decaf05bbee9b071f2acbd9473", size = 26202, upload-time = "2025-07-02T07:18:39.951Z" }, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "packaging" version = "25.0" @@ -417,24 +179,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -468,25 +212,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, ] -[[package]] -name = "psycopg2-binary" -version = "2.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload-time = "2024-10-16T11:21:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload-time = "2024-10-16T11:21:51.989Z" }, - { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload-time = "2024-10-16T11:21:57.584Z" }, - { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload-time = "2024-10-16T11:22:02.005Z" }, - { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload-time = "2024-10-16T11:22:06.412Z" }, - { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload-time = "2024-10-16T11:22:11.583Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload-time = "2024-10-16T11:22:16.406Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload-time = "2024-10-16T11:22:21.366Z" }, - { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload-time = "2024-10-16T11:22:25.684Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload-time = "2024-10-16T11:22:30.562Z" }, - { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload-time = "2025-01-04T20:09:19.234Z" }, -] - [[package]] name = "pycparser" version = "2.22" @@ -496,49 +221,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] -[[package]] -name = "pydantic" -version = "2.11.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.33.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -553,88 +235,28 @@ name = "pyinmor" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "black" }, - { name = "django" }, - { name = "django-click" }, - { name = "django-debug-toolbar" }, - { name = "django-extensions" }, - { name = "django-ninja" }, - { name = "django-redis" }, - { name = "django-types" }, - { name = "djangosaml2" }, { name = "httpx" }, - { name = "humanreadable" }, { name = "jwcrypto" }, { name = "port-for" }, - { name = "psycopg2-binary" }, - { name = "pydantic" }, - { name = "pysaml2" }, { name = "pytest" }, - { name = "pytest-django" }, + { name = "pytest-dotenv" }, { name = "pytest-redis" }, - { name = "pytz" }, + { name = "python-dotenv" }, { name = "redis" }, { name = "ruff" }, - { name = "ty" }, - { name = "types-jwcrypto" }, ] [package.metadata] requires-dist = [ - { name = "black" }, - { name = "django" }, - { name = "django-click" }, - { name = "django-debug-toolbar" }, - { name = "django-extensions" }, - { name = "django-ninja" }, - { name = "django-redis" }, - { name = "django-types" }, - { name = "djangosaml2" }, { name = "httpx" }, - { name = "humanreadable" }, { name = "jwcrypto" }, { name = "port-for" }, - { name = "psycopg2-binary" }, - { name = "pydantic" }, - { name = "pysaml2" }, { name = "pytest" }, - { name = "pytest-django" }, + { name = "pytest-dotenv" }, { name = "pytest-redis" }, - { name = "pytz" }, + { name = "python-dotenv" }, { name = "redis" }, { name = "ruff" }, - { name = "ty" }, - { name = "types-jwcrypto" }, -] - -[[package]] -name = "pyopenssl" -version = "24.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/70/ff56a63248562e77c0c8ee4aefc3224258f1856977e0c1472672b62dadb8/pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95", size = 184323, upload-time = "2024-07-20T17:26:31.252Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/dd/e0aa7ebef5168c75b772eda64978c597a9129b46be17779054652a7999e4/pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d", size = 58390, upload-time = "2024-07-20T17:26:29.057Z" }, -] - -[[package]] -name = "pysaml2" -version = "7.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "defusedxml" }, - { name = "pyopenssl" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "requests" }, - { name = "xmlschema" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/5e/21cee84df6f14b506b20231a8c777c1fe25c1d90e197ad495978255a58c9/pysaml2-7.5.2.tar.gz", hash = "sha256:529fd58107c49a9fd0e98fe545094072262d6533c6acf1124102eab170797de2", size = 340843, upload-time = "2025-02-10T21:51:31.179Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/0b/2ad218423e0608f279e2e3ceda7ff5415e76467cd6d6d1406756c9dd5b6f/pysaml2-7.5.2-py3-none-any.whl", hash = "sha256:6f7dc0cc3a72772821ebc92e7ee8df213945f8f9be766c647fb026eba4ca90fe", size = 420817, upload-time = "2025-02-10T21:51:22.597Z" }, ] [[package]] @@ -654,15 +276,16 @@ wheels = [ ] [[package]] -name = "pytest-django" -version = "4.11.1" +name = "pytest-dotenv" +version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, + { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/b0/cafee9c627c1bae228eb07c9977f679b3a7cb111b488307ab9594ba9e4da/pytest-dotenv-0.5.2.tar.gz", hash = "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732", size = 3782, upload-time = "2020-06-16T12:38:03.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/d0/da/9da67c67b3d0963160e3d2cbc7c38b6fae342670cc8e6d5936644b2cf944/pytest_dotenv-0.5.2-py3-none-any.whl", hash = "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f", size = 3993, upload-time = "2020-06-16T12:38:01.139Z" }, ] [[package]] @@ -681,24 +304,12 @@ wheels = [ ] [[package]] -name = "python-dateutil" -version = "2.9.0.post0" +name = "python-dotenv" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]] @@ -710,54 +321,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, ] -[[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, -] - [[package]] name = "ruff" -version = "0.12.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/eb/8c073deb376e46ae767f4961390d17545e8535921d2f65101720ed8bd434/ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", size = 5310076, upload-time = "2025-08-21T18:23:22.595Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/e7/560d049d15585d6c201f9eeacd2fd130def3741323e5ccf123786e0e3c95/ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", size = 11935161, upload-time = "2025-08-21T18:22:26.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b0/ad2464922a1113c365d12b8f80ed70fcfb39764288ac77c995156080488d/ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", size = 12660884, upload-time = "2025-08-21T18:22:30.925Z" }, - { url = "https://files.pythonhosted.org/packages/d7/f1/97f509b4108d7bae16c48389f54f005b62ce86712120fd8b2d8e88a7cb49/ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", size = 11872754, upload-time = "2025-08-21T18:22:34.035Z" }, - { url = "https://files.pythonhosted.org/packages/12/ad/44f606d243f744a75adc432275217296095101f83f966842063d78eee2d3/ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", size = 12092276, upload-time = "2025-08-21T18:22:36.764Z" }, - { url = "https://files.pythonhosted.org/packages/06/1f/ed6c265e199568010197909b25c896d66e4ef2c5e1c3808caf461f6f3579/ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", size = 11734700, upload-time = "2025-08-21T18:22:39.822Z" }, - { url = "https://files.pythonhosted.org/packages/63/c5/b21cde720f54a1d1db71538c0bc9b73dee4b563a7dd7d2e404914904d7f5/ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", size = 13468783, upload-time = "2025-08-21T18:22:42.559Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/39369e6ac7f2a1848f22fb0b00b690492f20811a1ac5c1fd1d2798329263/ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", size = 14436642, upload-time = "2025-08-21T18:22:45.612Z" }, - { url = "https://files.pythonhosted.org/packages/e3/03/5da8cad4b0d5242a936eb203b58318016db44f5c5d351b07e3f5e211bb89/ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", size = 13859107, upload-time = "2025-08-21T18:22:48.886Z" }, - { url = "https://files.pythonhosted.org/packages/19/19/dd7273b69bf7f93a070c9cec9494a94048325ad18fdcf50114f07e6bf417/ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", size = 12886521, upload-time = "2025-08-21T18:22:51.567Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1d/b4207ec35e7babaee62c462769e77457e26eb853fbdc877af29417033333/ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", size = 13097528, upload-time = "2025-08-21T18:22:54.609Z" }, - { url = "https://files.pythonhosted.org/packages/ff/00/58f7b873b21114456e880b75176af3490d7a2836033779ca42f50de3b47a/ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", size = 13080443, upload-time = "2025-08-21T18:22:57.413Z" }, - { url = "https://files.pythonhosted.org/packages/12/8c/9e6660007fb10189ccb78a02b41691288038e51e4788bf49b0a60f740604/ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60", size = 11896759, upload-time = "2025-08-21T18:23:00.473Z" }, - { url = "https://files.pythonhosted.org/packages/67/4c/6d092bb99ea9ea6ebda817a0e7ad886f42a58b4501a7e27cd97371d0ba54/ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", size = 11701463, upload-time = "2025-08-21T18:23:03.211Z" }, - { url = "https://files.pythonhosted.org/packages/59/80/d982c55e91df981f3ab62559371380616c57ffd0172d96850280c2b04fa8/ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", size = 12691603, upload-time = "2025-08-21T18:23:06.935Z" }, - { url = "https://files.pythonhosted.org/packages/ad/37/63a9c788bbe0b0850611669ec6b8589838faf2f4f959647f2d3e320383ae/ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", size = 13164356, upload-time = "2025-08-21T18:23:10.225Z" }, - { url = "https://files.pythonhosted.org/packages/47/d4/1aaa7fb201a74181989970ebccd12f88c0fc074777027e2a21de5a90657e/ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", size = 11896089, upload-time = "2025-08-21T18:23:14.232Z" }, - { url = "https://files.pythonhosted.org/packages/ad/14/2ad38fd4037daab9e023456a4a40ed0154e9971f8d6aed41bdea390aabd9/ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", size = 13004616, upload-time = "2025-08-21T18:23:17.422Z" }, - { url = "https://files.pythonhosted.org/packages/24/3c/21cf283d67af33a8e6ed242396863af195a8a6134ec581524fd22b9811b6/ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", size = 12074225, upload-time = "2025-08-21T18:23:20.137Z" }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +version = "0.12.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103, upload-time = "2025-08-28T13:59:08.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885, upload-time = "2025-08-28T13:58:26.654Z" }, + { url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364, upload-time = "2025-08-28T13:58:30.256Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111, upload-time = "2025-08-28T13:58:33.677Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060, upload-time = "2025-08-28T13:58:35.74Z" }, + { url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848, upload-time = "2025-08-28T13:58:38.051Z" }, + { url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288, upload-time = "2025-08-28T13:58:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633, upload-time = "2025-08-28T13:58:42.285Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430, upload-time = "2025-08-28T13:58:44.641Z" }, + { url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133, upload-time = "2025-08-28T13:58:47.039Z" }, + { url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082, upload-time = "2025-08-28T13:58:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490, upload-time = "2025-08-28T13:58:51.593Z" }, + { url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928, upload-time = "2025-08-28T13:58:53.943Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513, upload-time = "2025-08-28T13:58:55.976Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154, upload-time = "2025-08-28T13:58:58.16Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653, upload-time = "2025-08-28T13:59:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270, upload-time = "2025-08-28T13:59:02.347Z" }, + { url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600, upload-time = "2025-08-28T13:59:04.751Z" }, + { url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290, upload-time = "2025-08-28T13:59:06.933Z" }, ] [[package]] @@ -769,120 +356,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] -[[package]] -name = "sqlparse" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, -] - -[[package]] -name = "ty" -version = "0.0.1a19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/04/281c1a3c9c53dae5826b9d01a3412de653e3caf1ca50ce1265da66e06d73/ty-0.0.1a19.tar.gz", hash = "sha256:894f6a13a43989c8ef891ae079b3b60a0c0eae00244abbfbbe498a3840a235ac", size = 4098412, upload-time = "2025-08-19T13:29:58.559Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/65/a61cfcc7248b0257a3110bf98d3d910a4729c1063abdbfdcd1cad9012323/ty-0.0.1a19-py3-none-linux_armv6l.whl", hash = "sha256:e0e7762f040f4bab1b37c57cb1b43cc3bc5afb703fa5d916dfcafa2ef885190e", size = 8143744, upload-time = "2025-08-19T13:29:13.88Z" }, - { url = "https://files.pythonhosted.org/packages/02/d9/232afef97d9afa2274d23a4c49a3ad690282ca9696e1b6bbb6e4e9a1b072/ty-0.0.1a19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cd0a67ac875f49f34d9a0b42dcabf4724194558a5dd36867209d5695c67768f7", size = 8305799, upload-time = "2025-08-19T13:29:17.322Z" }, - { url = "https://files.pythonhosted.org/packages/20/14/099d268da7a9cccc6ba38dfc124f6742a1d669bc91f2c61a3465672b4f71/ty-0.0.1a19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ff8b1c0b85137333c39eccd96c42603af8ba7234d6e2ed0877f66a4a26750dd4", size = 7901431, upload-time = "2025-08-19T13:29:21.635Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cd/3f1ca6e1d7f77cc4d08910a3fc4826313c031c0aae72286ae859e737670c/ty-0.0.1a19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fef34a29f4b97d78aa30e60adbbb12137cf52b8b2b0f1a408dd0feb0466908a", size = 8051501, upload-time = "2025-08-19T13:29:23.741Z" }, - { url = "https://files.pythonhosted.org/packages/47/72/ddbec39f48ce3f5f6a3fa1f905c8fff2873e59d2030f738814032bd783e3/ty-0.0.1a19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0f219cb43c0c50fc1091f8ebd5548d3ef31ee57866517b9521d5174978af9fd", size = 7981234, upload-time = "2025-08-19T13:29:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/f2/0f/58e76b8d4634df066c790d362e8e73b25852279cd6f817f099b42a555a66/ty-0.0.1a19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22abb6c1f14c65c1a2fafd38e25dd3c87994b3ab88cb0b323235b51dbad082d9", size = 8916394, upload-time = "2025-08-19T13:29:27.932Z" }, - { url = "https://files.pythonhosted.org/packages/70/30/01bfd93ccde11540b503e2539e55f6a1fc6e12433a229191e248946eb753/ty-0.0.1a19-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5b49225c349a3866e38dd297cb023a92d084aec0e895ed30ca124704bff600e6", size = 9412024, upload-time = "2025-08-19T13:29:30.942Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a2/2216d752f5f22c5c0995f9b13f18337301220f2a7d952c972b33e6a63583/ty-0.0.1a19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88f41728b3b07402e0861e3c34412ca963268e55f6ab1690208f25d37cb9d63c", size = 9032657, upload-time = "2025-08-19T13:29:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/24/c7/e6650b0569be1b69a03869503d07420c9fb3e90c9109b09726c44366ce63/ty-0.0.1a19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33814a1197ec3e930fcfba6fb80969fe7353957087b42b88059f27a173f7510b", size = 8812775, upload-time = "2025-08-19T13:29:36.505Z" }, - { url = "https://files.pythonhosted.org/packages/35/c6/b8a20e06b97fe8203059d56d8f91cec4f9633e7ba65f413d80f16aa0be04/ty-0.0.1a19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71b7f2b674a287258f628acafeecd87691b169522945ff6192cd8a69af15857", size = 8631417, upload-time = "2025-08-19T13:29:38.837Z" }, - { url = "https://files.pythonhosted.org/packages/be/99/821ca1581dcf3d58ffb7bbe1cde7e1644dbdf53db34603a16a459a0b302c/ty-0.0.1a19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a7f8ef9ac4c38e8651c18c7380649c5a3fa9adb1a6012c721c11f4bbdc0ce24", size = 7928900, upload-time = "2025-08-19T13:29:41.08Z" }, - { url = "https://files.pythonhosted.org/packages/08/cb/59f74a0522e57565fef99e2287b2bc803ee47ff7dac250af26960636939f/ty-0.0.1a19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:60f40e72f0fbf4e54aa83d9a6cb1959f551f83de73af96abbb94711c1546bd60", size = 8003310, upload-time = "2025-08-19T13:29:43.165Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b3/1209b9acb5af00a2755114042e48fb0f71decc20d9d77a987bf5b3d1a102/ty-0.0.1a19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:64971e4d3e3f83dc79deb606cc438255146cab1ab74f783f7507f49f9346d89d", size = 8496463, upload-time = "2025-08-19T13:29:46.136Z" }, - { url = "https://files.pythonhosted.org/packages/a2/d6/a4b6ba552d347a08196d83a4d60cb23460404a053dd3596e23a922bce544/ty-0.0.1a19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9aadbff487e2e1486e83543b4f4c2165557f17432369f419be9ba48dc47625ca", size = 8700633, upload-time = "2025-08-19T13:29:49.351Z" }, - { url = "https://files.pythonhosted.org/packages/96/c5/258f318d68b95685c8d98fb654a38882c9d01ce5d9426bed06124f690f04/ty-0.0.1a19-py3-none-win32.whl", hash = "sha256:00b75b446357ee22bcdeb837cb019dc3bc1dc5e5013ff0f46a22dfe6ce498fe2", size = 7811441, upload-time = "2025-08-19T13:29:52.077Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bb/039227eee3c0c0cddc25f45031eea0f7f10440713f12d333f2f29cf8e934/ty-0.0.1a19-py3-none-win_amd64.whl", hash = "sha256:aaef76b2f44f6379c47adfe58286f0c56041cb2e374fd8462ae8368788634469", size = 8441186, upload-time = "2025-08-19T13:29:54.53Z" }, - { url = "https://files.pythonhosted.org/packages/74/5f/bceb29009670ae6f759340f9cb434121bc5ed84ad0f07bdc6179eaaa3204/ty-0.0.1a19-py3-none-win_arm64.whl", hash = "sha256:893755bb35f30653deb28865707e3b16907375c830546def2741f6ff9a764710", size = 8000810, upload-time = "2025-08-19T13:29:56.796Z" }, -] - -[[package]] -name = "typepy" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mbstrdecoder" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/59/4c39942077d7de285f762a91024dbda731be693591732977358f77d120fb/typepy-1.3.4.tar.gz", hash = "sha256:89c1f66de6c6133209c43a94d23431d320ba03ef5db18f241091ea594035d9de", size = 39558, upload-time = "2024-12-29T09:18:15.774Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/31/e393c3830bdedd01735bd195c85ac3034b6bcaf6c18142bab60a4047ca36/typepy-1.3.4-py3-none-any.whl", hash = "sha256:d5ed3e0c7f49521bff0603dd08cf8d453371cf68d65a29d3d0038552ccc46e2e", size = 31449, upload-time = "2024-12-29T09:18:13.135Z" }, -] - -[[package]] -name = "types-jwcrypto" -version = "1.5.0.20250516" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/97/90/db563c5ea97518d28abbbac06ca937895033f3311115490fe7313026b5fd/types_jwcrypto-1.5.0.20250516.tar.gz", hash = "sha256:7ca1878c6fed2bb7a046cf832b28d3d5340deb84f1bf5b3831d09257c7f1d030", size = 11573, upload-time = "2025-05-16T03:08:59.965Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/2e/2db48fa4f7ed1e619c68d954cbb244453b44dbede7dca605f56d6c26018b/types_jwcrypto-1.5.0.20250516-py3-none-any.whl", hash = "sha256:0fdeac3412bb2737f233d1176487a506d225fdd026e3a8e0208d123313c3c7cd", size = 13015, upload-time = "2025-05-16T03:08:59.011Z" }, -] - -[[package]] -name = "types-psycopg2" -version = "2.9.21.20250809" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/17/d0/66f3f04bab48bfdb2c8b795b2b3e75eb20c7d1fb0516916db3be6aa4a683/types_psycopg2-2.9.21.20250809.tar.gz", hash = "sha256:b7c2cbdcf7c0bd16240f59ba694347329b0463e43398de69784ea4dee45f3c6d", size = 26539, upload-time = "2025-08-09T03:14:54.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/98/182497602921c47fadc8470d51a32e5c75343c8931c0b572a5c4ae3b948b/types_psycopg2-2.9.21.20250809-py3-none-any.whl", hash = "sha256:59b7b0ed56dcae9efae62b8373497274fc1a0484bdc5135cdacbe5a8f44e1d7b", size = 24824, upload-time = "2025-08-09T03:14:53.908Z" }, -] - [[package]] name = "typing-extensions" -version = "4.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, -] - -[[package]] -name = "tzdata" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, -] - -[[package]] -name = "urllib3" -version = "2.5.0" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, -] - -[[package]] -name = "xmlschema" -version = "2.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "elementpath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/af/42e9e773eaa6bc8e8c322f93c75454b0d370979048b250bfef7786ff26ec/xmlschema-2.5.1.tar.gz", hash = "sha256:4f7497de6c8b6dc2c28ad7b9ed6e21d186f4afe248a5bea4f54eedab4da44083", size = 539267, upload-time = "2023-12-19T15:51:57.663Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/2a/c2bc97fd20efe65cfcfc21666d1b0213969133d37ea093761d264d9ed9f8/xmlschema-2.5.1-py3-none-any.whl", hash = "sha256:ec2b2a15c8896c1fcd14dcee34ca30032b99456c3c43ce793fdb9dca2fb4b869", size = 395065, upload-time = "2023-12-19T15:51:53.136Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] From 9cbc48c5bdf37b744453c0599f1a3e74040e103a Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 09:17:07 +0200 Subject: [PATCH 13/85] Uses dotenv for db host and port --- admin/inmoradmin/settings.py | 5 +++-- admin/manage.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/admin/inmoradmin/settings.py b/admin/inmoradmin/settings.py index 4d03efe..454ea0d 100644 --- a/admin/inmoradmin/settings.py +++ b/admin/inmoradmin/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/ """ +import os from pathlib import Path from typing import List @@ -83,8 +84,8 @@ "NAME": "postgres", "USER": "postgres", "PASSWORD": "", - "HOST": "db", - "PORT": 5432, + "HOST": os.environ.get("DB_HOST", "db"), + "PORT": os.environ.get("DB_PORT", 5432), } } diff --git a/admin/manage.py b/admin/manage.py index 231503c..1313229 100755 --- a/admin/manage.py +++ b/admin/manage.py @@ -4,6 +4,10 @@ import os import sys +from dotenv import load_dotenv + +load_dotenv() + def main(): """Run administrative tasks.""" From 0077cd49b07323f0228bae753a6887defd4284cb Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 09:18:17 +0200 Subject: [PATCH 14/85] Adds pyproject.toml for admin Django admin portal gets own pyproject.toml --- admin/pyproject.toml | 97 +++++ admin/uv.lock | 914 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1011 insertions(+) create mode 100644 admin/pyproject.toml create mode 100644 admin/uv.lock diff --git a/admin/pyproject.toml b/admin/pyproject.toml new file mode 100644 index 0000000..54d4c91 --- /dev/null +++ b/admin/pyproject.toml @@ -0,0 +1,97 @@ +[project] +name = "inmoradmin" +authors = [{name = "Kushal Das", email = "kushal@sunet.se"}] +version = "0.1.0" +description = "Inmor Trust Anchor Admin portal" +license = { file="LICENSE" } +requires-python = '>=3.13' + +dependencies = [ + "jwcrypto", + "httpx", + "redis", + "django", + "django-types", + "black", + "ruff", + "redis", + "django-extensions", + "django-debug-toolbar", + "django-ninja", + "pytz", + "psycopg2-binary", + "pysaml2", + "djangosaml2", + "django-redis", + "jwcrypto", + "types-jwcrypto", + "httpx", + "django-click", + "pydantic", + "ty", + "port-for", + "pytest-redis", + "pytest", + "pytest-django", + "humanreadable", + "python-dotenv", + "pytest-dotenv", +] + +[project.urls] +Home = "https://github.com/SUNET/inmor" + +[tool.pyright] +stubPath = "typings" +reportAny = false +reportUnknownMemberType = false +reportUnknownVariableType = false +reportUnknownArgumentType = false +reportUnannotatedClassAttribute = false +reportExplicitAny = false +reportImplicitOverride = false +reportUninitializedInstanceVariable = false + +[tool.ruff] +line-length = 100 +exclude = [ + "db", + "redis", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "django_redis", +] + +[tool.ty.src] +exclude = [ + "db", +] + +[tool.pytest.ini_options] +testpaths = "tests" +pythonpath = ["."] +DJANGO_SETTINGS_MODULE = "inmoradmin.settings" +env_files = [".env"] diff --git a/admin/uv.lock b/admin/uv.lock new file mode 100644 index 0000000..24343d5 --- /dev/null +++ b/admin/uv.lock @@ -0,0 +1,914 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "asgiref" +version = "3.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142", size = 36870, upload-time = "2025-07-08T09:07:43.344Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "43.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989, upload-time = "2024-10-18T15:58:32.918Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303, upload-time = "2024-10-18T15:57:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905, upload-time = "2024-10-18T15:57:39.166Z" }, + { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271, upload-time = "2024-10-18T15:57:41.227Z" }, + { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606, upload-time = "2024-10-18T15:57:42.903Z" }, + { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484, upload-time = "2024-10-18T15:57:45.434Z" }, + { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131, upload-time = "2024-10-18T15:57:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647, upload-time = "2024-10-18T15:57:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873, upload-time = "2024-10-18T15:57:51.822Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039, upload-time = "2024-10-18T15:57:54.426Z" }, + { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984, upload-time = "2024-10-18T15:57:56.174Z" }, + { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968, upload-time = "2024-10-18T15:57:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754, upload-time = "2024-10-18T15:58:00.683Z" }, + { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458, upload-time = "2024-10-18T15:58:02.225Z" }, + { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220, upload-time = "2024-10-18T15:58:04.331Z" }, + { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898, upload-time = "2024-10-18T15:58:06.113Z" }, + { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592, upload-time = "2024-10-18T15:58:08.673Z" }, + { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145, upload-time = "2024-10-18T15:58:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026, upload-time = "2024-10-18T15:58:11.916Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "django" +version = "5.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/9b/779f853c3d2d58b9e08346061ff3e331cdec3fe3f53aae509e256412a593/django-5.2.5.tar.gz", hash = "sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae", size = 10859748, upload-time = "2025-08-06T08:26:29.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/6e/98a1d23648e0085bb5825326af17612ecd8fc76be0ce96ea4dc35e17b926/django-5.2.5-py3-none-any.whl", hash = "sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd", size = 8302999, upload-time = "2025-08-06T08:26:23.562Z" }, +] + +[[package]] +name = "django-click" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/bb/f47eb64d008805195a5d44a2fb306638a84e673dd5fd4ee738b05fa3a55b/django_click-2.4.1.tar.gz", hash = "sha256:5b989ca791689391e6744f8a4dda9bc0aac06670f408c45794d6b83fbe7577ce", size = 8742, upload-time = "2025-02-27T13:19:28.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/f1/3bffa13296a9ee51a266e49db00a7defb64f613d68a80cdb2d50721f4b65/django_click-2.4.1-py2.py3-none-any.whl", hash = "sha256:80cfa57af0fae29a3d47f20a74d71aecd1f0bf299549cddb97d98a949e130f01", size = 7334, upload-time = "2025-02-27T13:19:26.426Z" }, +] + +[[package]] +name = "django-debug-toolbar" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "sqlparse" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/d5/5fc90234532088aeec5faa48d5b09951cc7eab6626030ed427d3bd8cd9bc/django_debug_toolbar-6.0.0.tar.gz", hash = "sha256:6eb9fa6f4a5884bf04004700ffb5a44043f1fff38784447fc52c1633448c8c14", size = 305331, upload-time = "2025-07-25T13:11:48.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/b5/4724a8c18fcc5b09dca7b7a0e70c34208317bb110075ad12484d6588ae91/django_debug_toolbar-6.0.0-py3-none-any.whl", hash = "sha256:0cf2cac5c307b77d6e143c914e5c6592df53ffe34642d93929e5ef095ae56841", size = 266967, upload-time = "2025-07-25T13:11:47.265Z" }, +] + +[[package]] +name = "django-extensions" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/b3/ed0f54ed706ec0b54fd251cc0364a249c6cd6c6ec97f04dc34be5e929eac/django_extensions-4.1.tar.gz", hash = "sha256:7b70a4d28e9b840f44694e3f7feb54f55d495f8b3fa6c5c0e5e12bcb2aa3cdeb", size = 283078, upload-time = "2025-04-11T01:15:39.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" }, +] + +[[package]] +name = "django-ninja" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/1a/f0d051f54375cfd2310f803925993fdc4172e41e627d63ece48194b07892/django_ninja-1.4.3.tar.gz", hash = "sha256:e46d477ca60c228d2a5eb3cc912094928ea830d364501f966661eeada67cb038", size = 3709571, upload-time = "2025-06-04T15:11:13.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/ec/0cfa9b817f048cdec354354ae0569d7c0fd63907e5b1f927a7ee04a18635/django_ninja-1.4.3-py3-none-any.whl", hash = "sha256:f3204137a059437b95677049474220611f1cf9efedba9213556474b75168fa01", size = 2426185, upload-time = "2025-06-04T15:11:11.314Z" }, +] + +[[package]] +name = "django-redis" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/53/dbcfa1e528e0d6c39947092625b2c89274b5d88f14d357cee53c4d6dbbd4/django_redis-6.0.0.tar.gz", hash = "sha256:2d9cb12a20424a4c4dde082c6122f486628bae2d9c2bee4c0126a4de7fda00dd", size = 56904, upload-time = "2025-06-17T18:15:46.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/79/055dfcc508cfe9f439d9f453741188d633efa9eab90fc78a67b0ab50b137/django_redis-6.0.0-py3-none-any.whl", hash = "sha256:20bf0063a8abee567eb5f77f375143c32810c8700c0674ced34737f8de4e36c0", size = 33687, upload-time = "2025-06-17T18:15:34.165Z" }, +] + +[[package]] +name = "django-types" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-psycopg2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/26/5f2873f7208dee0791710fda494a8d3ef7fb1d34785069b55168a23e2ac2/django_types-0.22.0.tar.gz", hash = "sha256:4cecc9eee846e7ff2a398bec9dfe6543e76efb922a7a58c5d6064bcb0e6a3dc5", size = 187214, upload-time = "2025-07-15T01:05:48.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3a/0ecbaab07cfe7b0a4fd72dfde4be8e7c11aad2b88c816cfcbb800c14cbda/django_types-0.22.0-py3-none-any.whl", hash = "sha256:ba15c756c7a732e58afd0737e54489f1c5e6f1bd24132e9199c637b1f88b057c", size = 376869, upload-time = "2025-07-15T01:05:46.621Z" }, +] + +[[package]] +name = "djangosaml2" +version = "1.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, + { name = "django" }, + { name = "pysaml2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/69/90444e1fe4fd65e42768c8ec08d590b6cfdf01a8441bf44cb2005f5117cc/djangosaml2-1.11.1.tar.gz", hash = "sha256:f4fcbba980e6f3ad5b47f1f2d0f7296bcee4e37e2ee8237e44b28efe77f2d383", size = 57721, upload-time = "2025-07-08T09:50:03.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/7a/fabc6a31a328e7a3121828a6911df039faf225bd4ae03fc45f1e689514f0/djangosaml2-1.11.1-py2.py3-none-any.whl", hash = "sha256:9109aaab247cabc7c76ab00d61d34e2813c902538dd7071f7f6bd26e39aadb28", size = 67560, upload-time = "2025-07-08T09:50:01.273Z" }, +] + +[[package]] +name = "elementpath" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/41/afdd82534c80e9675d1c51dc21d0889b72d023bfe395a2f5a44d751d3a73/elementpath-4.8.0.tar.gz", hash = "sha256:5822a2560d99e2633d95f78694c7ff9646adaa187db520da200a8e9479dc46ae", size = 358528, upload-time = "2025-03-03T20:51:08.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/95/615af832e7f507fe5ce4562b4be1bd2fec080c4ff6da88dcd0c2dbfca582/elementpath-4.8.0-py3-none-any.whl", hash = "sha256:5393191f84969bcf8033b05ec4593ef940e58622ea13cefe60ecefbbf09d58d9", size = 243271, upload-time = "2025-03-03T20:51:03.027Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "humanreadable" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typepy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/7a/11b6b14db6f0525ef9052016fa1790fda09fff3b7a24f142213d90113230/humanreadable-0.4.1.tar.gz", hash = "sha256:4350eba873975939dc65bd6a64b1ad885418ff58d9c6693435507382d42a22f3", size = 18494, upload-time = "2025-05-04T05:35:07.231Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/73/e3af126a1399f99e55cc982a486a25173700b3a3e117165767ecd5cab5a8/humanreadable-0.4.1-py3-none-any.whl", hash = "sha256:1341e7b52ab2b884aebc7bc41b2bb4c6bd47b9719598648e11e133e6254aeb4f", size = 11601, upload-time = "2025-05-04T05:35:04.696Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "inmoradmin" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "black" }, + { name = "django" }, + { name = "django-click" }, + { name = "django-debug-toolbar" }, + { name = "django-extensions" }, + { name = "django-ninja" }, + { name = "django-redis" }, + { name = "django-types" }, + { name = "djangosaml2" }, + { name = "httpx" }, + { name = "humanreadable" }, + { name = "jwcrypto" }, + { name = "port-for" }, + { name = "psycopg2-binary" }, + { name = "pydantic" }, + { name = "pysaml2" }, + { name = "pytest" }, + { name = "pytest-django" }, + { name = "pytest-dotenv" }, + { name = "pytest-redis" }, + { name = "python-dotenv" }, + { name = "pytz" }, + { name = "redis" }, + { name = "ruff" }, + { name = "ty" }, + { name = "types-jwcrypto" }, +] + +[package.metadata] +requires-dist = [ + { name = "black" }, + { name = "django" }, + { name = "django-click" }, + { name = "django-debug-toolbar" }, + { name = "django-extensions" }, + { name = "django-ninja" }, + { name = "django-redis" }, + { name = "django-types" }, + { name = "djangosaml2" }, + { name = "httpx" }, + { name = "humanreadable" }, + { name = "jwcrypto" }, + { name = "port-for" }, + { name = "psycopg2-binary" }, + { name = "pydantic" }, + { name = "pysaml2" }, + { name = "pytest" }, + { name = "pytest-django" }, + { name = "pytest-dotenv" }, + { name = "pytest-redis" }, + { name = "python-dotenv" }, + { name = "pytz" }, + { name = "redis" }, + { name = "ruff" }, + { name = "ty" }, + { name = "types-jwcrypto" }, +] + +[[package]] +name = "jwcrypto" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/db/870e5d5fb311b0bcf049630b5ba3abca2d339fd5e13ba175b4c13b456d08/jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039", size = 87168, upload-time = "2024-03-06T19:58:31.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520, upload-time = "2024-03-06T19:58:29.765Z" }, +] + +[[package]] +name = "mbstrdecoder" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/ab/05ae008357c8bdb6245ebf8a101d99f26c096e0ea20800b318153da23796/mbstrdecoder-1.1.4.tar.gz", hash = "sha256:8105ef9cf6b7d7d69fe7fd6b68a2d8f281ca9b365d7a9b670be376b2e6c81b21", size = 14527, upload-time = "2025-01-18T10:07:31.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/ac/5ce64a1d4cce00390beab88622a290420401f1cabf05caf2fc0995157c21/mbstrdecoder-1.1.4-py3-none-any.whl", hash = "sha256:03dae4ec50ec0d2ff4743e63fdbd5e0022815857494d35224b60775d3d934a8c", size = 7933, upload-time = "2025-01-18T10:07:29.562Z" }, +] + +[[package]] +name = "mirakuru" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "psutil", marker = "sys_platform != 'cygwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/57/bfa1e5b904b18f669e03b7c6981bb92fb473b7da9c3b082a875e25bfaa8c/mirakuru-2.6.1.tar.gz", hash = "sha256:95d4f5a5ad406a625e9ca418f20f8e09386a35dad1ea30fd9073e0ae93f712c7", size = 26889, upload-time = "2025-07-02T07:18:41.234Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/ce/139df7074328119869a1041ce91c082d78287541cf867f9c4c85097c5d8b/mirakuru-2.6.1-py3-none-any.whl", hash = "sha256:4be0bfd270744454fa0c0466b8127b66bd55f4decaf05bbee9b071f2acbd9473", size = 26202, upload-time = "2025-07-02T07:18:39.951Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "port-for" +version = "0.7.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/84/ad5114c85217426d7a5170a74a6f9d6b724df117c2f3b75e41fc9d6c6811/port_for-0.7.4.tar.gz", hash = "sha256:fc7713e7b22f89442f335ce12536653656e8f35146739eccaeff43d28436028d", size = 25077, upload-time = "2024-10-09T12:28:38.875Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/a2/579dcefbb0285b31f8d65b537f8a9932ed51319e0a3694e01b5bbc271f92/port_for-0.7.4-py3-none-any.whl", hash = "sha256:08404aa072651a53dcefe8d7a598ee8a1dca320d9ac44ac464da16ccf2a02c4a", size = 21369, upload-time = "2024-10-09T12:28:37.853Z" }, +] + +[[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, upload-time = "2025-02-13T21:54:07.946Z" } +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, upload-time = "2025-02-13T21:54:12.36Z" }, + { 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, upload-time = "2025-02-13T21:54:16.07Z" }, + { 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, upload-time = "2025-02-13T21:54:18.662Z" }, + { 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, upload-time = "2025-02-13T21:54:21.811Z" }, + { 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, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload-time = "2024-10-16T11:21:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload-time = "2024-10-16T11:21:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload-time = "2024-10-16T11:21:57.584Z" }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload-time = "2024-10-16T11:22:02.005Z" }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload-time = "2024-10-16T11:22:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload-time = "2024-10-16T11:22:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload-time = "2024-10-16T11:22:16.406Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload-time = "2024-10-16T11:22:21.366Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload-time = "2024-10-16T11:22:25.684Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload-time = "2024-10-16T11:22:30.562Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload-time = "2025-01-04T20:09:19.234Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyopenssl" +version = "24.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/70/ff56a63248562e77c0c8ee4aefc3224258f1856977e0c1472672b62dadb8/pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95", size = 184323, upload-time = "2024-07-20T17:26:31.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/dd/e0aa7ebef5168c75b772eda64978c597a9129b46be17779054652a7999e4/pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d", size = 58390, upload-time = "2024-07-20T17:26:29.057Z" }, +] + +[[package]] +name = "pysaml2" +version = "7.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "defusedxml" }, + { name = "pyopenssl" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "requests" }, + { name = "xmlschema" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/5e/21cee84df6f14b506b20231a8c777c1fe25c1d90e197ad495978255a58c9/pysaml2-7.5.2.tar.gz", hash = "sha256:529fd58107c49a9fd0e98fe545094072262d6533c6acf1124102eab170797de2", size = 340843, upload-time = "2025-02-10T21:51:31.179Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/0b/2ad218423e0608f279e2e3ceda7ff5415e76467cd6d6d1406756c9dd5b6f/pysaml2-7.5.2-py3-none-any.whl", hash = "sha256:6f7dc0cc3a72772821ebc92e7ee8df213945f8f9be766c647fb026eba4ca90fe", size = 420817, upload-time = "2025-02-10T21:51:22.597Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-django" +version = "4.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" }, +] + +[[package]] +name = "pytest-dotenv" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/b0/cafee9c627c1bae228eb07c9977f679b3a7cb111b488307ab9594ba9e4da/pytest-dotenv-0.5.2.tar.gz", hash = "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732", size = 3782, upload-time = "2020-06-16T12:38:03.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/da/9da67c67b3d0963160e3d2cbc7c38b6fae342670cc8e6d5936644b2cf944/pytest_dotenv-0.5.2-py3-none-any.whl", hash = "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f", size = 3993, upload-time = "2020-06-16T12:38:01.139Z" }, +] + +[[package]] +name = "pytest-redis" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mirakuru" }, + { name = "port-for" }, + { name = "pytest" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/d4/4d37bbe92ce7e991175115a30335dd591dfc9a086b10b5ed58133b286a17/pytest_redis-3.1.3.tar.gz", hash = "sha256:8bb76be4a749f1907c8b4f04213df40b679949cc2ffe39657e222ccb912aecd9", size = 38202, upload-time = "2024-11-27T08:42:22.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/5f/d9e617368aeee75609e43c66ff22e9d216c761f5b4290d56927d493ec618/pytest_redis-3.1.3-py3-none-any.whl", hash = "sha256:7fd6eb54ed0878590b857e1011b031c38aa3e230a53771739e845d3fc6b05d79", size = 32856, upload-time = "2024-11-27T08:42:19.837Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "redis" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/eb/8c073deb376e46ae767f4961390d17545e8535921d2f65101720ed8bd434/ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", size = 5310076, upload-time = "2025-08-21T18:23:22.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/e7/560d049d15585d6c201f9eeacd2fd130def3741323e5ccf123786e0e3c95/ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", size = 11935161, upload-time = "2025-08-21T18:22:26.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b0/ad2464922a1113c365d12b8f80ed70fcfb39764288ac77c995156080488d/ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", size = 12660884, upload-time = "2025-08-21T18:22:30.925Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f1/97f509b4108d7bae16c48389f54f005b62ce86712120fd8b2d8e88a7cb49/ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", size = 11872754, upload-time = "2025-08-21T18:22:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/12/ad/44f606d243f744a75adc432275217296095101f83f966842063d78eee2d3/ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", size = 12092276, upload-time = "2025-08-21T18:22:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/06/1f/ed6c265e199568010197909b25c896d66e4ef2c5e1c3808caf461f6f3579/ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", size = 11734700, upload-time = "2025-08-21T18:22:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/63/c5/b21cde720f54a1d1db71538c0bc9b73dee4b563a7dd7d2e404914904d7f5/ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", size = 13468783, upload-time = "2025-08-21T18:22:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/39369e6ac7f2a1848f22fb0b00b690492f20811a1ac5c1fd1d2798329263/ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", size = 14436642, upload-time = "2025-08-21T18:22:45.612Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/5da8cad4b0d5242a936eb203b58318016db44f5c5d351b07e3f5e211bb89/ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", size = 13859107, upload-time = "2025-08-21T18:22:48.886Z" }, + { url = "https://files.pythonhosted.org/packages/19/19/dd7273b69bf7f93a070c9cec9494a94048325ad18fdcf50114f07e6bf417/ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", size = 12886521, upload-time = "2025-08-21T18:22:51.567Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1d/b4207ec35e7babaee62c462769e77457e26eb853fbdc877af29417033333/ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", size = 13097528, upload-time = "2025-08-21T18:22:54.609Z" }, + { url = "https://files.pythonhosted.org/packages/ff/00/58f7b873b21114456e880b75176af3490d7a2836033779ca42f50de3b47a/ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", size = 13080443, upload-time = "2025-08-21T18:22:57.413Z" }, + { url = "https://files.pythonhosted.org/packages/12/8c/9e6660007fb10189ccb78a02b41691288038e51e4788bf49b0a60f740604/ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60", size = 11896759, upload-time = "2025-08-21T18:23:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/4c/6d092bb99ea9ea6ebda817a0e7ad886f42a58b4501a7e27cd97371d0ba54/ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", size = 11701463, upload-time = "2025-08-21T18:23:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/59/80/d982c55e91df981f3ab62559371380616c57ffd0172d96850280c2b04fa8/ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", size = 12691603, upload-time = "2025-08-21T18:23:06.935Z" }, + { url = "https://files.pythonhosted.org/packages/ad/37/63a9c788bbe0b0850611669ec6b8589838faf2f4f959647f2d3e320383ae/ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", size = 13164356, upload-time = "2025-08-21T18:23:10.225Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/1aaa7fb201a74181989970ebccd12f88c0fc074777027e2a21de5a90657e/ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", size = 11896089, upload-time = "2025-08-21T18:23:14.232Z" }, + { url = "https://files.pythonhosted.org/packages/ad/14/2ad38fd4037daab9e023456a4a40ed0154e9971f8d6aed41bdea390aabd9/ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", size = 13004616, upload-time = "2025-08-21T18:23:17.422Z" }, + { url = "https://files.pythonhosted.org/packages/24/3c/21cf283d67af33a8e6ed242396863af195a8a6134ec581524fd22b9811b6/ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", size = 12074225, upload-time = "2025-08-21T18:23:20.137Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, +] + +[[package]] +name = "ty" +version = "0.0.1a19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/04/281c1a3c9c53dae5826b9d01a3412de653e3caf1ca50ce1265da66e06d73/ty-0.0.1a19.tar.gz", hash = "sha256:894f6a13a43989c8ef891ae079b3b60a0c0eae00244abbfbbe498a3840a235ac", size = 4098412, upload-time = "2025-08-19T13:29:58.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/65/a61cfcc7248b0257a3110bf98d3d910a4729c1063abdbfdcd1cad9012323/ty-0.0.1a19-py3-none-linux_armv6l.whl", hash = "sha256:e0e7762f040f4bab1b37c57cb1b43cc3bc5afb703fa5d916dfcafa2ef885190e", size = 8143744, upload-time = "2025-08-19T13:29:13.88Z" }, + { url = "https://files.pythonhosted.org/packages/02/d9/232afef97d9afa2274d23a4c49a3ad690282ca9696e1b6bbb6e4e9a1b072/ty-0.0.1a19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cd0a67ac875f49f34d9a0b42dcabf4724194558a5dd36867209d5695c67768f7", size = 8305799, upload-time = "2025-08-19T13:29:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/20/14/099d268da7a9cccc6ba38dfc124f6742a1d669bc91f2c61a3465672b4f71/ty-0.0.1a19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ff8b1c0b85137333c39eccd96c42603af8ba7234d6e2ed0877f66a4a26750dd4", size = 7901431, upload-time = "2025-08-19T13:29:21.635Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/3f1ca6e1d7f77cc4d08910a3fc4826313c031c0aae72286ae859e737670c/ty-0.0.1a19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fef34a29f4b97d78aa30e60adbbb12137cf52b8b2b0f1a408dd0feb0466908a", size = 8051501, upload-time = "2025-08-19T13:29:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/47/72/ddbec39f48ce3f5f6a3fa1f905c8fff2873e59d2030f738814032bd783e3/ty-0.0.1a19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0f219cb43c0c50fc1091f8ebd5548d3ef31ee57866517b9521d5174978af9fd", size = 7981234, upload-time = "2025-08-19T13:29:25.839Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0f/58e76b8d4634df066c790d362e8e73b25852279cd6f817f099b42a555a66/ty-0.0.1a19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22abb6c1f14c65c1a2fafd38e25dd3c87994b3ab88cb0b323235b51dbad082d9", size = 8916394, upload-time = "2025-08-19T13:29:27.932Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/01bfd93ccde11540b503e2539e55f6a1fc6e12433a229191e248946eb753/ty-0.0.1a19-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5b49225c349a3866e38dd297cb023a92d084aec0e895ed30ca124704bff600e6", size = 9412024, upload-time = "2025-08-19T13:29:30.942Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a2/2216d752f5f22c5c0995f9b13f18337301220f2a7d952c972b33e6a63583/ty-0.0.1a19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88f41728b3b07402e0861e3c34412ca963268e55f6ab1690208f25d37cb9d63c", size = 9032657, upload-time = "2025-08-19T13:29:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/24/c7/e6650b0569be1b69a03869503d07420c9fb3e90c9109b09726c44366ce63/ty-0.0.1a19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33814a1197ec3e930fcfba6fb80969fe7353957087b42b88059f27a173f7510b", size = 8812775, upload-time = "2025-08-19T13:29:36.505Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/b8a20e06b97fe8203059d56d8f91cec4f9633e7ba65f413d80f16aa0be04/ty-0.0.1a19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71b7f2b674a287258f628acafeecd87691b169522945ff6192cd8a69af15857", size = 8631417, upload-time = "2025-08-19T13:29:38.837Z" }, + { url = "https://files.pythonhosted.org/packages/be/99/821ca1581dcf3d58ffb7bbe1cde7e1644dbdf53db34603a16a459a0b302c/ty-0.0.1a19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a7f8ef9ac4c38e8651c18c7380649c5a3fa9adb1a6012c721c11f4bbdc0ce24", size = 7928900, upload-time = "2025-08-19T13:29:41.08Z" }, + { url = "https://files.pythonhosted.org/packages/08/cb/59f74a0522e57565fef99e2287b2bc803ee47ff7dac250af26960636939f/ty-0.0.1a19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:60f40e72f0fbf4e54aa83d9a6cb1959f551f83de73af96abbb94711c1546bd60", size = 8003310, upload-time = "2025-08-19T13:29:43.165Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b3/1209b9acb5af00a2755114042e48fb0f71decc20d9d77a987bf5b3d1a102/ty-0.0.1a19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:64971e4d3e3f83dc79deb606cc438255146cab1ab74f783f7507f49f9346d89d", size = 8496463, upload-time = "2025-08-19T13:29:46.136Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d6/a4b6ba552d347a08196d83a4d60cb23460404a053dd3596e23a922bce544/ty-0.0.1a19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9aadbff487e2e1486e83543b4f4c2165557f17432369f419be9ba48dc47625ca", size = 8700633, upload-time = "2025-08-19T13:29:49.351Z" }, + { url = "https://files.pythonhosted.org/packages/96/c5/258f318d68b95685c8d98fb654a38882c9d01ce5d9426bed06124f690f04/ty-0.0.1a19-py3-none-win32.whl", hash = "sha256:00b75b446357ee22bcdeb837cb019dc3bc1dc5e5013ff0f46a22dfe6ce498fe2", size = 7811441, upload-time = "2025-08-19T13:29:52.077Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/039227eee3c0c0cddc25f45031eea0f7f10440713f12d333f2f29cf8e934/ty-0.0.1a19-py3-none-win_amd64.whl", hash = "sha256:aaef76b2f44f6379c47adfe58286f0c56041cb2e374fd8462ae8368788634469", size = 8441186, upload-time = "2025-08-19T13:29:54.53Z" }, + { url = "https://files.pythonhosted.org/packages/74/5f/bceb29009670ae6f759340f9cb434121bc5ed84ad0f07bdc6179eaaa3204/ty-0.0.1a19-py3-none-win_arm64.whl", hash = "sha256:893755bb35f30653deb28865707e3b16907375c830546def2741f6ff9a764710", size = 8000810, upload-time = "2025-08-19T13:29:56.796Z" }, +] + +[[package]] +name = "typepy" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mbstrdecoder" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/59/4c39942077d7de285f762a91024dbda731be693591732977358f77d120fb/typepy-1.3.4.tar.gz", hash = "sha256:89c1f66de6c6133209c43a94d23431d320ba03ef5db18f241091ea594035d9de", size = 39558, upload-time = "2024-12-29T09:18:15.774Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/31/e393c3830bdedd01735bd195c85ac3034b6bcaf6c18142bab60a4047ca36/typepy-1.3.4-py3-none-any.whl", hash = "sha256:d5ed3e0c7f49521bff0603dd08cf8d453371cf68d65a29d3d0038552ccc46e2e", size = 31449, upload-time = "2024-12-29T09:18:13.135Z" }, +] + +[[package]] +name = "types-jwcrypto" +version = "1.5.0.20250516" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/90/db563c5ea97518d28abbbac06ca937895033f3311115490fe7313026b5fd/types_jwcrypto-1.5.0.20250516.tar.gz", hash = "sha256:7ca1878c6fed2bb7a046cf832b28d3d5340deb84f1bf5b3831d09257c7f1d030", size = 11573, upload-time = "2025-05-16T03:08:59.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/2e/2db48fa4f7ed1e619c68d954cbb244453b44dbede7dca605f56d6c26018b/types_jwcrypto-1.5.0.20250516-py3-none-any.whl", hash = "sha256:0fdeac3412bb2737f233d1176487a506d225fdd026e3a8e0208d123313c3c7cd", size = 13015, upload-time = "2025-05-16T03:08:59.011Z" }, +] + +[[package]] +name = "types-psycopg2" +version = "2.9.21.20250809" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/d0/66f3f04bab48bfdb2c8b795b2b3e75eb20c7d1fb0516916db3be6aa4a683/types_psycopg2-2.9.21.20250809.tar.gz", hash = "sha256:b7c2cbdcf7c0bd16240f59ba694347329b0463e43398de69784ea4dee45f3c6d", size = 26539, upload-time = "2025-08-09T03:14:54.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/98/182497602921c47fadc8470d51a32e5c75343c8931c0b572a5c4ae3b948b/types_psycopg2-2.9.21.20250809-py3-none-any.whl", hash = "sha256:59b7b0ed56dcae9efae62b8373497274fc1a0484bdc5135cdacbe5a8f44e1d7b", size = 24824, upload-time = "2025-08-09T03:14:53.908Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "xmlschema" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "elementpath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/af/42e9e773eaa6bc8e8c322f93c75454b0d370979048b250bfef7786ff26ec/xmlschema-2.5.1.tar.gz", hash = "sha256:4f7497de6c8b6dc2c28ad7b9ed6e21d186f4afe248a5bea4f54eedab4da44083", size = 539267, upload-time = "2023-12-19T15:51:57.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/2a/c2bc97fd20efe65cfcfc21666d1b0213969133d37ea093761d264d9ed9f8/xmlschema-2.5.1-py3-none-any.whl", hash = "sha256:ec2b2a15c8896c1fcd14dcee34ca30032b99456c3c43ce793fdb9dca2fb4b869", size = 395065, upload-time = "2023-12-19T15:51:53.136Z" }, +] From 78b2db6c9fd745ece3d16f0205dfd4de6dc4911c Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 09:18:47 +0200 Subject: [PATCH 15/85] Updates just commands Now we use `uv` based pytest runs. Python linting happens only inside of the `admin` directory. --- justfile | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/justfile b/justfile index 0d4ea24..34f63dc 100644 --- a/justfile +++ b/justfile @@ -2,37 +2,49 @@ _default: @just --list --unsorted -venv_command := if path_exists(".venv") == "false" { "uv venv && uv pip install -r admin/requirements-dev.txt" } else { "" } - # To create the virtual environment for development +[working-directory: 'admin'] venv: - #{{ venv_command }} uv sync # To create a development environment -dev: venv +dev: uv run scripts/create-keys.py -# To check for formatting and typing erors -lint: venv +# To check for formatting and clippy error in TA +lint-rust: + cargo clippy + cargo fmt --check + +# To check for formatting and typing errors in Admin +[working-directory: 'admin'] +lint-python: venv # The Python code is not packaged, so imports are currently # relative to the admin/ directory . .venv/bin/activate && \ - ty check . --extra-search-path=admin/ && \ + ty check . && \ ruff format --check && \ ruff check . - cargo clippy - cargo fmt --check + +# Lint target for both rust and python +lint: lint-rust lint-python # To run inmor tests -test: venv +test-ta: # We have integration tests for the inmor rust binary - . .venv/bin/activate && \ - pytest -vvv + uv run pytest -vvv + +# To run django tests for admin +[working-directory: 'admin'] +test-admin: + uv run pytest -vvv + +# Test target for both rust and django code +test: test-ta test-admin # To format the Rust and Python code -reformat: venv - ruff format +reformat: + uv run ruff format cargo fmt # Building rust binary to be able to be mounted From c5cc6d988f5b88e81155b5b1bb608056b0dab516 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 11:44:05 +0200 Subject: [PATCH 16/85] Excludes db.json from gitignore This will be our test fixture. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 46c3b90..5958bca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /target *.json +!db.json # Byte-compiled / optimized / DLL files From e896484d311b4373300dc5b2209666754fa8ccb1 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 11:44:54 +0200 Subject: [PATCH 17/85] Adds initial API tests We have one test for the Admin API. --- admin/db.json | 1 + admin/tests/conftest.py | 17 +++++++++++++++++ admin/tests/test_api.py | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 admin/db.json create mode 100644 admin/tests/conftest.py create mode 100644 admin/tests/test_api.py diff --git a/admin/db.json b/admin/db.json new file mode 100644 index 0000000..863f954 --- /dev/null +++ b/admin/db.json @@ -0,0 +1 @@ +[{"model": "trustmarks.trustmarktype", "pk": 1, "fields": {"tmtype": "https://sunet.se/does_not_exist_trustmark", "valid_for": 365}}, {"model": "trustmarks.trustmarktype", "pk": 2, "fields": {"tmtype": "https://example.com/trust_mark", "valid_for": 365}}, {"model": "trustmarks.trustmark", "pk": 1, "fields": {"tmt": 1, "added": "2025-08-19T08:14:37.203Z", "domain": "https://fakerp0.labb.sunet.se", "active": true}}, {"model": "trustmarks.trustmark", "pk": 2, "fields": {"tmt": 1, "added": "2025-08-19T08:14:53.265Z", "domain": "https://fakeop0.labb.sunet.se", "active": true}}, {"model": "trustmarks.trustmark", "pk": 3, "fields": {"tmt": 1, "added": "2025-08-19T08:14:58.640Z", "domain": "https://fakerp1.labb.sunet.se", "active": true}}, {"model": "entities.subordinate", "pk": 1, "fields": {"added": "2025-08-19T08:43:46.065Z", "entityid": "https://fakerp0.labb.sunet.se"}}, {"model": "entities.subordinate", "pk": 2, "fields": {"added": "2025-08-19T08:43:57.230Z", "entityid": "https://fakerp1.labb.sunet.se"}}, {"model": "entities.subordinate", "pk": 3, "fields": {"added": "2025-08-19T08:44:02.802Z", "entityid": "https://fakeop0.labb.sunet.se"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add trust mark type", "content_type": 1, "codename": "add_trustmarktype"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change trust mark type", "content_type": 1, "codename": "change_trustmarktype"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete trust mark type", "content_type": 1, "codename": "delete_trustmarktype"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view trust mark type", "content_type": 1, "codename": "view_trustmarktype"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add trust mark", "content_type": 2, "codename": "add_trustmark"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change trust mark", "content_type": 2, "codename": "change_trustmark"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete trust mark", "content_type": 2, "codename": "delete_trustmark"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view trust mark", "content_type": 2, "codename": "view_trustmark"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add subordinate", "content_type": 3, "codename": "add_subordinate"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change subordinate", "content_type": 3, "codename": "change_subordinate"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete subordinate", "content_type": 3, "codename": "delete_subordinate"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view subordinate", "content_type": 3, "codename": "view_subordinate"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add log entry", "content_type": 4, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change log entry", "content_type": 4, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete log entry", "content_type": 4, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view log entry", "content_type": 4, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add permission", "content_type": 5, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change permission", "content_type": 5, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete permission", "content_type": 5, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view permission", "content_type": 5, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add group", "content_type": 6, "codename": "add_group"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change group", "content_type": 6, "codename": "change_group"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete group", "content_type": 6, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view group", "content_type": 6, "codename": "view_group"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add user", "content_type": 7, "codename": "add_user"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change user", "content_type": 7, "codename": "change_user"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete user", "content_type": 7, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view user", "content_type": 7, "codename": "view_user"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add content type", "content_type": 8, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change content type", "content_type": 8, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete content type", "content_type": 8, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view content type", "content_type": 8, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can add session", "content_type": 9, "codename": "add_session"}}, {"model": "auth.permission", "pk": 34, "fields": {"name": "Can change session", "content_type": 9, "codename": "change_session"}}, {"model": "auth.permission", "pk": 35, "fields": {"name": "Can delete session", "content_type": 9, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 36, "fields": {"name": "Can view session", "content_type": 9, "codename": "view_session"}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "trustmarks", "model": "trustmarktype"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "trustmarks", "model": "trustmark"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "entities", "model": "subordinate"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "sessions", "model": "session"}}] \ No newline at end of file diff --git a/admin/tests/conftest.py b/admin/tests/conftest.py new file mode 100644 index 0000000..ef3ac14 --- /dev/null +++ b/admin/tests/conftest.py @@ -0,0 +1,17 @@ +import os +import sys + +import pytest +from dotenv import load_dotenv + +load_dotenv() + +sys.path.append(os.path.dirname(os.path.dirname(__file__))) + +from django.core.management import call_command + + +@pytest.fixture +def db(request, django_db_setup, django_db_blocker): + django_db_blocker.unblock() + call_command("loaddata", "db.json") diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py new file mode 100644 index 0000000..984a6e2 --- /dev/null +++ b/admin/tests/test_api.py @@ -0,0 +1,21 @@ +from django.test import TestCase +from ninja.testing import TestClient + +from inmoradmin.api import router + + +def test_trustmarktypes_list(db): + # don't forget to import router from code above + self = TestCase() + trustmark_list = [ + {"tmtype": "https://sunet.se/does_not_exist_trustmark", "valid_for": 365}, + {"tmtype": "https://example.com/trust_mark", "valid_for": 365}, + ] + client: TestClient = TestClient(router) + response = client.get("/trust_mark_type/list") + + self.assertEqual(response.status_code, 200) + marks = response.json() + self.assertEqual(marks["count"], 2) + self.assertEqual(marks["items"], trustmark_list) + From 19cc663f38f1078cd587b237d7690fca57ee78ad Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 11:50:18 +0200 Subject: [PATCH 18/85] Updates tests action to use db --- .github/workflows/tests.yml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 03f3a8d..8a530c9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,6 +10,20 @@ on: jobs: lint: runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_HOST_AUTH_METHOD: trust + POSTGRES_USER: postgres + POSTGRES_DB: postgres + ports: + # will assign a random free host port + - 5432/tcp + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: @@ -37,5 +51,13 @@ jobs: - name: Build TA appliation run: cargo build - - name: Run tests for inmor - run: just test + - name: Run tests for inmor TA + run: just test-ta + + - name: Run tests for inmor Admin API + run: just test-admin + env: + # use localhost for the host here because we are running the job on the VM. + # If we were running the job on in a container this would be postgres + DB_HOST: localhost + DB_PORT: ${{ job.services.postgres.ports[5432] }} # get randomly assigned published port From 3b6ea50881847a349228035b55acfd8b0d3ea7b5 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 11:56:12 +0200 Subject: [PATCH 19/85] Reformats by ruff --- admin/tests/test_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 984a6e2..49e2083 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -18,4 +18,3 @@ def test_trustmarktypes_list(db): marks = response.json() self.assertEqual(marks["count"], 2) self.assertEqual(marks["items"], trustmark_list) - From 985789733511d0f8df1dd6e24f66779ad019146f Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 11:56:25 +0200 Subject: [PATCH 20/85] Adds keys for django ADMIN portal --- .gitignore | 2 ++ admin/private.json | 1 + admin/public.json | 1 + 3 files changed, 4 insertions(+) create mode 100644 admin/private.json create mode 100644 admin/public.json diff --git a/.gitignore b/.gitignore index 5958bca..c9641c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /target *.json !db.json +!private.json +!public.json # Byte-compiled / optimized / DLL files diff --git a/admin/private.json b/admin/private.json new file mode 100644 index 0000000..ab7d685 --- /dev/null +++ b/admin/private.json @@ -0,0 +1 @@ +{"d":"WBRC3sK6_Vxq890dV-Dx6HvLJbr8Jzz5FY7M7kf1sn57yGY_QhEXwcTNtHNkBK8YxkFN5doNOnlVBxOI5XOjOFi9QpNRrcNSXhj8Upn5dPap4SzkLP5J4dcR6uWCWzxg1t3DWYPQPPVwYv1o-tUL2TvK0O7WesEM2X50eVeDsdxduS87H-76Rsj3vU4d_TYS40-Vun90RMzl7zOdEv2nrxSoXX36_aCav42Uke1MDUSxsG__9BURct6VmzkZB4eZYyvIhqloeLoDZmSbggejePM16WZvN1t_DD8D2U6vbMFJe5a9yXM74NhX7WjJxcEclS_H7B5aajlKpM0b9nodYQ","dp":"LreoYmmQo9PQYesF8hERj7iU5et3m8_dFWlFNgEsUv3BTs0Q8YcEFhll56orOM_w-LCG0Y2BKam5wgBYFk0b-e3r0DKVz_4Ss1anboMK4-37lesbYxV29YTW9B-O2E4WiZhfyvfsdqvFKNwytjwvxSGrFUxO7aEuUE7E2OlyKDc","dq":"fPL_ioDLPO9CRIz1TPJXHG4oKGAZwkAL0QmUm3pwE0GfMwOpBj7cb4Tl5KoFS56H2plGlEutox-YM6L9ub75U4xQ24QxSpOeA6eKZoWZAaZxTwyHHI9cckevmYjHV8ClOBRl4nEt6pC2iToJnPBJitK_an4WUww6iFKztlM_vcE","e":"AQAB","kid":"NbZ2P_LHOpbtEoWQ3ukPR_pnii3Yi8hE7j4ghZaGLOI","kty":"RSA","n":"tZvpP09nS3RHnpP2ussXP2Q7ACdyt2720IUjcFJglq2FIjCgwzwKwEnV-9Y4EsZqshfbhjBuFn3ZfQ-NYb2wFkLg0SdlDC2F3NzdAGWktMR6lRJdv7tesXjVe2iXvtkflxeLPeOdhUCxhCBH2DqfkgKYBOkEUl-p36pJsYlnSfvvC9g_9J5htrLlNFHn1vH8eVweOO-e8HPqJstGzzkO-ZkcnTXLA0wSyE-P3_jAmRPEm_DsveWktMwZ5jHiEwo_rqCiSqA3qqa2vRRN6_QEh9gRNtp4WEkXLK5q0_VW8A_F9Z7z4Pv1JS1ZZxe6OyYA7l6CJynnZ1lrP1QilQZZYw","p":"5WTQVpQ-8C9XUqzP75AOcFKkCjYAtwwLEoQMpSZsrdBSywMLdkjNGPtqfY5Q4hTw1yfKLnYC3HAAiCTdxoUxkA_C5Mpt8-oIWfGDsgWW1WrOVxePL1WQd0xAO_exvdiBMVSEGgDLcCbk_KtjJsfb91uqviRom78Xt12aYF4eh1M","q":"yqxENdU0eRwNABI1b_E5jGn3k5R0Fy0GKlvU8nxPQvG_MwkfxKTAzpzp72ypiV0PVx1Um7pd6th1g--V8J7gNY_05fP3egMLhuQIgNlY7WtZxyo8oTqgihm189ZXCOVLJk4Y7mFFy7tgF2voUntDgsbqAl3UKXqf2SfKU-7N87E","qi":"dm_nDZRnhXqXFIIOnIU-6ulEz-PYd2vVikf6FdS777piEA_lyB9fTAN-k3Xicsxs4TwqiwyO494sHvf2ftC0VIcqX-oFEtKvCG1uOu3wWqoKjBi27C3EePwhfEQhc78AS8YgilwSfCp3F4bPGAc_NmLaijjRU4IJPiV7gPamtBE","use":"sig"} \ No newline at end of file diff --git a/admin/public.json b/admin/public.json new file mode 100644 index 0000000..b48de17 --- /dev/null +++ b/admin/public.json @@ -0,0 +1 @@ +{"e":"AQAB","kid":"NbZ2P_LHOpbtEoWQ3ukPR_pnii3Yi8hE7j4ghZaGLOI","kty":"RSA","n":"tZvpP09nS3RHnpP2ussXP2Q7ACdyt2720IUjcFJglq2FIjCgwzwKwEnV-9Y4EsZqshfbhjBuFn3ZfQ-NYb2wFkLg0SdlDC2F3NzdAGWktMR6lRJdv7tesXjVe2iXvtkflxeLPeOdhUCxhCBH2DqfkgKYBOkEUl-p36pJsYlnSfvvC9g_9J5htrLlNFHn1vH8eVweOO-e8HPqJstGzzkO-ZkcnTXLA0wSyE-P3_jAmRPEm_DsveWktMwZ5jHiEwo_rqCiSqA3qqa2vRRN6_QEh9gRNtp4WEkXLK5q0_VW8A_F9Z7z4Pv1JS1ZZxe6OyYA7l6CJynnZ1lrP1QilQZZYw","use":"sig"} \ No newline at end of file From e56627b5c823e89597ac13dbf5e0a918adf85df7 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 12:01:21 +0200 Subject: [PATCH 21/85] Reformats code for linting --- admin/tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/admin/tests/conftest.py b/admin/tests/conftest.py index ef3ac14..2fc1083 100644 --- a/admin/tests/conftest.py +++ b/admin/tests/conftest.py @@ -2,14 +2,13 @@ import sys import pytest +from django.core.management import call_command from dotenv import load_dotenv load_dotenv() sys.path.append(os.path.dirname(os.path.dirname(__file__))) -from django.core.management import call_command - @pytest.fixture def db(request, django_db_setup, django_db_blocker): From ac58008617a945faec003ce16eb455492d14ed82 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 12:26:37 +0200 Subject: [PATCH 22/85] Renames tests Github action job --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a530c9..04be42f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ on: pull_request: jobs: - lint: + tests: runs-on: ubuntu-latest services: From 9d436e397fd714f01a670e13ba0e457a9afe68b7 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:16:17 +0200 Subject: [PATCH 23/85] Moves typings into admin --- {typings => admin/typings}/django_redis/__init__.pyi | 0 {typings => admin/typings}/django_redis/cache.pyi | 0 {typings => admin/typings}/django_redis/client/__init__.pyi | 0 {typings => admin/typings}/django_redis/client/default.pyi | 0 {typings => admin/typings}/django_redis/client/herd.pyi | 0 {typings => admin/typings}/django_redis/client/sentinel.pyi | 0 {typings => admin/typings}/django_redis/client/sharded.pyi | 0 {typings => admin/typings}/django_redis/compressors/__init__.pyi | 0 {typings => admin/typings}/django_redis/exceptions.pyi | 0 {typings => admin/typings}/django_redis/hash_ring.pyi | 0 {typings => admin/typings}/django_redis/pool.pyi | 0 {typings => admin/typings}/django_redis/serializers/__init__.pyi | 0 {typings => admin/typings}/django_redis/util.pyi | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {typings => admin/typings}/django_redis/__init__.pyi (100%) rename {typings => admin/typings}/django_redis/cache.pyi (100%) rename {typings => admin/typings}/django_redis/client/__init__.pyi (100%) rename {typings => admin/typings}/django_redis/client/default.pyi (100%) rename {typings => admin/typings}/django_redis/client/herd.pyi (100%) rename {typings => admin/typings}/django_redis/client/sentinel.pyi (100%) rename {typings => admin/typings}/django_redis/client/sharded.pyi (100%) rename {typings => admin/typings}/django_redis/compressors/__init__.pyi (100%) rename {typings => admin/typings}/django_redis/exceptions.pyi (100%) rename {typings => admin/typings}/django_redis/hash_ring.pyi (100%) rename {typings => admin/typings}/django_redis/pool.pyi (100%) rename {typings => admin/typings}/django_redis/serializers/__init__.pyi (100%) rename {typings => admin/typings}/django_redis/util.pyi (100%) diff --git a/typings/django_redis/__init__.pyi b/admin/typings/django_redis/__init__.pyi similarity index 100% rename from typings/django_redis/__init__.pyi rename to admin/typings/django_redis/__init__.pyi diff --git a/typings/django_redis/cache.pyi b/admin/typings/django_redis/cache.pyi similarity index 100% rename from typings/django_redis/cache.pyi rename to admin/typings/django_redis/cache.pyi diff --git a/typings/django_redis/client/__init__.pyi b/admin/typings/django_redis/client/__init__.pyi similarity index 100% rename from typings/django_redis/client/__init__.pyi rename to admin/typings/django_redis/client/__init__.pyi diff --git a/typings/django_redis/client/default.pyi b/admin/typings/django_redis/client/default.pyi similarity index 100% rename from typings/django_redis/client/default.pyi rename to admin/typings/django_redis/client/default.pyi diff --git a/typings/django_redis/client/herd.pyi b/admin/typings/django_redis/client/herd.pyi similarity index 100% rename from typings/django_redis/client/herd.pyi rename to admin/typings/django_redis/client/herd.pyi diff --git a/typings/django_redis/client/sentinel.pyi b/admin/typings/django_redis/client/sentinel.pyi similarity index 100% rename from typings/django_redis/client/sentinel.pyi rename to admin/typings/django_redis/client/sentinel.pyi diff --git a/typings/django_redis/client/sharded.pyi b/admin/typings/django_redis/client/sharded.pyi similarity index 100% rename from typings/django_redis/client/sharded.pyi rename to admin/typings/django_redis/client/sharded.pyi diff --git a/typings/django_redis/compressors/__init__.pyi b/admin/typings/django_redis/compressors/__init__.pyi similarity index 100% rename from typings/django_redis/compressors/__init__.pyi rename to admin/typings/django_redis/compressors/__init__.pyi diff --git a/typings/django_redis/exceptions.pyi b/admin/typings/django_redis/exceptions.pyi similarity index 100% rename from typings/django_redis/exceptions.pyi rename to admin/typings/django_redis/exceptions.pyi diff --git a/typings/django_redis/hash_ring.pyi b/admin/typings/django_redis/hash_ring.pyi similarity index 100% rename from typings/django_redis/hash_ring.pyi rename to admin/typings/django_redis/hash_ring.pyi diff --git a/typings/django_redis/pool.pyi b/admin/typings/django_redis/pool.pyi similarity index 100% rename from typings/django_redis/pool.pyi rename to admin/typings/django_redis/pool.pyi diff --git a/typings/django_redis/serializers/__init__.pyi b/admin/typings/django_redis/serializers/__init__.pyi similarity index 100% rename from typings/django_redis/serializers/__init__.pyi rename to admin/typings/django_redis/serializers/__init__.pyi diff --git a/typings/django_redis/util.pyi b/admin/typings/django_redis/util.pyi similarity index 100% rename from typings/django_redis/util.pyi rename to admin/typings/django_redis/util.pyi From 64ecc819328113bf3d465599a13bc41f55909943 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:19:14 +0200 Subject: [PATCH 24/85] Adds just command to run django test on container --- justfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/justfile b/justfile index 34f63dc..81ede65 100644 --- a/justfile +++ b/justfile @@ -70,6 +70,9 @@ up: down: docker compose down +t-admin: + docker compose exec admin pytest -vvv + debug-ta: docker compose run --rm ta /bin/bash From 9da9f3c5611de29c808f9e6a9455302d71ae34ae Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:19:41 +0200 Subject: [PATCH 25/85] Updates Python container --- admin/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/Dockerfile b/admin/Dockerfile index 324f45e..c91f5ef 100644 --- a/admin/Dockerfile +++ b/admin/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11 +FROM python:3.13 RUN apt update && apt install xmlsec1 -y ENV PYTHONUNBUFFERED=1 WORKDIR /code From ec91379da99f7dc26221955e8e6f8f7ef6d80984 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:20:20 +0200 Subject: [PATCH 26/85] Updates requirements file --- admin/requirements-dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/admin/requirements-dev.txt b/admin/requirements-dev.txt index 65f1814..6f12e95 100644 --- a/admin/requirements-dev.txt +++ b/admin/requirements-dev.txt @@ -22,3 +22,5 @@ pytest django-ninja humanreadable pytest-django +python-dotenv +pytest-dotenv From c45dfe8485eaad8add7aceb3e89a729bacea77bf Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:20:40 +0200 Subject: [PATCH 27/85] Adds TA_DEFAULTS for system --- admin/inmoradmin/settings.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/admin/inmoradmin/settings.py b/admin/inmoradmin/settings.py index 454ea0d..ffa44c5 100644 --- a/admin/inmoradmin/settings.py +++ b/admin/inmoradmin/settings.py @@ -151,9 +151,18 @@ # We must have this, empty dictionary is okay METADATA_POLICY = {} -# DATABASES = { -# "default": { -# "ENGINE": "django.db.backends.sqlite3", -# "NAME": BASE_DIR / "db.sqlite3", -# } -# } +# The following are the default values the system will use while creating new entries via API. +TA_DEFAULTS = { + "trustmarktype": { + "autorenew": True, + "valid_for": 8760, + "renewal_time": 48, + "active": True, + }, + "trustmark": { + "autorenew": True, + "valid_for": 8760, + "renewal_time": 48, + "active": True, + }, +} From 2f4b7875b57e67895e5f3ff3eb9c0b0c7092473f Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:33:55 +0200 Subject: [PATCH 28/85] Updates model based on new API A fresh start for the DB structure based API discussion. --- admin/trustmarks/migrations/0001_initial.py | 34 +++++++++++++++---- .../0002_alter_trustmark_unique_together.py | 16 --------- ...pe_valid_for_alter_trustmarktype_tmtype.py | 22 ------------ admin/trustmarks/models.py | 11 ++++-- 4 files changed, 36 insertions(+), 47 deletions(-) delete mode 100644 admin/trustmarks/migrations/0002_alter_trustmark_unique_together.py delete mode 100644 admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py diff --git a/admin/trustmarks/migrations/0001_initial.py b/admin/trustmarks/migrations/0001_initial.py index 09a995d..fad587a 100644 --- a/admin/trustmarks/migrations/0001_initial.py +++ b/admin/trustmarks/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.3 on 2025-07-03 09:03 +# Generated by Django 5.2.6 on 2025-09-04 13:10 import django.db.models.deletion import django.db.models.functions.datetime @@ -6,6 +6,7 @@ class Migration(migrations.Migration): + initial = True dependencies = [] @@ -23,10 +24,18 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("tmtype", models.CharField()), + ("tmtype", models.CharField(unique=True)), + ("autorenew", models.BooleanField(default=False)), + ("valid_for", models.IntegerField(default=8760)), + ("renewal_time", models.IntegerField(default=48)), + ("active", models.BooleanField(default=True)), ], options={ - "indexes": [models.Index(fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx")], + "indexes": [ + models.Index( + fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx" + ) + ], }, ), migrations.CreateModel( @@ -43,10 +52,15 @@ class Migration(migrations.Migration): ), ( "added", - models.DateTimeField(db_default=django.db.models.functions.datetime.Now()), + models.DateTimeField( + db_default=django.db.models.functions.datetime.Now() + ), ), ("domain", models.CharField()), - ("active", models.BooleanField(default=False)), + ("active", models.BooleanField()), + ("autorenew", models.BooleanField()), + ("valid_for", models.IntegerField()), + ("renewal_time", models.IntegerField()), ( "tmt", models.ForeignKey( @@ -57,9 +71,15 @@ class Migration(migrations.Migration): ], options={ "indexes": [ - models.Index(fields=["domain"], name="trustmarks__domain_101ea9_idx"), - models.Index(fields=["active"], name="trustmarks__active_292a4b_idx"), + models.Index( + fields=["domain"], name="trustmarks__domain_101ea9_idx" + ), + models.Index( + fields=["active"], name="trustmarks__active_292a4b_idx" + ), + models.Index(fields=["tmt"], name="trustmarks__tmt_id_4c0dfb_idx"), ], + "unique_together": {("tmt", "domain")}, }, ), ] diff --git a/admin/trustmarks/migrations/0002_alter_trustmark_unique_together.py b/admin/trustmarks/migrations/0002_alter_trustmark_unique_together.py deleted file mode 100644 index 287636c..0000000 --- a/admin/trustmarks/migrations/0002_alter_trustmark_unique_together.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 5.2.3 on 2025-07-04 10:41 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("trustmarks", "0001_initial"), - ] - - operations = [ - migrations.AlterUniqueTogether( - name="trustmark", - unique_together={("tmt", "domain")}, - ), - ] diff --git a/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py b/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py deleted file mode 100644 index d34b63b..0000000 --- a/admin/trustmarks/migrations/0003_trustmarktype_valid_for_alter_trustmarktype_tmtype.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.2.4 on 2025-09-01 10:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("trustmarks", "0002_alter_trustmark_unique_together"), - ] - - operations = [ - migrations.AddField( - model_name="trustmarktype", - name="valid_for", - field=models.IntegerField(default=365), - ), - migrations.AlterField( - model_name="trustmarktype", - name="tmtype", - field=models.CharField(unique=True), - ), - ] diff --git a/admin/trustmarks/models.py b/admin/trustmarks/models.py index ac36b78..9fc8cc7 100644 --- a/admin/trustmarks/models.py +++ b/admin/trustmarks/models.py @@ -6,7 +6,10 @@ class TrustMarkType(models.Model): id: int tmtype = models.CharField(unique=True) - valid_for = models.IntegerField(default=365) # Means by default it is valid for 365 days + autorenew = models.BooleanField(default=False) + valid_for = models.IntegerField(default=8760) # Means 365 days + renewal_time = models.IntegerField(default=48) # Means 2 days + active = models.BooleanField(default=True) # Means active by default def __str__(self): return self.tmtype @@ -22,7 +25,10 @@ class TrustMark(models.Model): tmt = models.ForeignKey(TrustMarkType, on_delete=models.CASCADE) added = models.DateTimeField(db_default=Now()) domain = models.CharField() - active = models.BooleanField(default=False) + active = models.BooleanField() + autorenew = models.BooleanField() + valid_for = models.IntegerField() + renewal_time = models.IntegerField() def __str__(self): return self.domain @@ -32,4 +38,5 @@ class Meta: indexes = [ models.Index(fields=["domain"]), models.Index(fields=["active"]), + models.Index(fields=["tmt"]), ] From fc418bba5f2ce3762a7de35baa85ce6e984baa62 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:36:30 +0200 Subject: [PATCH 29/85] Adds new api for creating trustmarktype --- admin/inmoradmin/api.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 482ab52..8f17fb2 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,15 +1,24 @@ +from typing import Any + +from django.conf import settings from django.http import HttpRequest from ninja import NinjaAPI, Router, Schema from ninja.pagination import LimitOffsetPagination, paginate + from trustmarks.models import TrustMarkType api = NinjaAPI() router = Router() +DEFAULTS: dict[str, dict[str, Any]] = settings.TA_DEFAULTS + class TrustMarkTypeSchema(Schema): tmtype: str - valid_for: int = 365 # How many days the default entry will be valid for + autorenew: bool = DEFAULTS["trustmarktype"]["autorenew"] + valid_for: int = DEFAULTS["trustmarktype"]["valid_for"] + renewal_time: int = DEFAULTS["trustmarktype"]["renewal_time"] + active: bool = DEFAULTS["trustmarktype"]["active"] class Message(Schema): @@ -17,15 +26,19 @@ class Message(Schema): id: int = 0 -@router.post("/trust_mark_type", response={200: Message, 403: Message, 500: Message}) +@router.post("/trustmarktypes", response={201: Message, 403: Message, 500: Message}) def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): """Creates a new trust_mark_type""" try: tmt, created = TrustMarkType.objects.get_or_create( - tmtype=data.tmtype, valid_for=data.valid_for + tmtype=data.tmtype, + autorenew=data.autorenew, + valid_for=data.valid_for, + renewal_time=data.renewal_time, + active=data.active, ) if created: - return {"message": "TrustMarkType created Succesfully.", "id": tmt.id} + return 201, {"message": "TrustMarkType created Succesfully.", "id": tmt.id} else: return 403, {"message": "TrustMarkType already existed.", "id": tmt.id} except Exception as e: From b6711e0f2a21c623e4ddff5dd40bbac2317e8823 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:57:48 +0200 Subject: [PATCH 30/85] Updates /trustmarktypes to get a list --- admin/inmoradmin/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 8f17fb2..069b34c 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -46,7 +46,7 @@ def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): return 500, {"message": "Error while creating a new TrustMarkType"} -@router.get("/trust_mark_type/list", response=list[TrustMarkTypeSchema]) +@router.get("/trustmarktypes", response=list[TrustMarkTypeSchema]) @paginate(LimitOffsetPagination) def list_trust_mark_type( request: HttpRequest, From efd9ef19f0be6b7ea56b35a9cbaf594c4a0be69f Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 15:58:13 +0200 Subject: [PATCH 31/85] Updates test for listing trustmarktypes --- admin/db.json | 2 +- admin/tests/test_api.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/admin/db.json b/admin/db.json index 863f954..fce6c32 100644 --- a/admin/db.json +++ b/admin/db.json @@ -1 +1 @@ -[{"model": "trustmarks.trustmarktype", "pk": 1, "fields": {"tmtype": "https://sunet.se/does_not_exist_trustmark", "valid_for": 365}}, {"model": "trustmarks.trustmarktype", "pk": 2, "fields": {"tmtype": "https://example.com/trust_mark", "valid_for": 365}}, {"model": "trustmarks.trustmark", "pk": 1, "fields": {"tmt": 1, "added": "2025-08-19T08:14:37.203Z", "domain": "https://fakerp0.labb.sunet.se", "active": true}}, {"model": "trustmarks.trustmark", "pk": 2, "fields": {"tmt": 1, "added": "2025-08-19T08:14:53.265Z", "domain": "https://fakeop0.labb.sunet.se", "active": true}}, {"model": "trustmarks.trustmark", "pk": 3, "fields": {"tmt": 1, "added": "2025-08-19T08:14:58.640Z", "domain": "https://fakerp1.labb.sunet.se", "active": true}}, {"model": "entities.subordinate", "pk": 1, "fields": {"added": "2025-08-19T08:43:46.065Z", "entityid": "https://fakerp0.labb.sunet.se"}}, {"model": "entities.subordinate", "pk": 2, "fields": {"added": "2025-08-19T08:43:57.230Z", "entityid": "https://fakerp1.labb.sunet.se"}}, {"model": "entities.subordinate", "pk": 3, "fields": {"added": "2025-08-19T08:44:02.802Z", "entityid": "https://fakeop0.labb.sunet.se"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add trust mark type", "content_type": 1, "codename": "add_trustmarktype"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change trust mark type", "content_type": 1, "codename": "change_trustmarktype"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete trust mark type", "content_type": 1, "codename": "delete_trustmarktype"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view trust mark type", "content_type": 1, "codename": "view_trustmarktype"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add trust mark", "content_type": 2, "codename": "add_trustmark"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change trust mark", "content_type": 2, "codename": "change_trustmark"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete trust mark", "content_type": 2, "codename": "delete_trustmark"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view trust mark", "content_type": 2, "codename": "view_trustmark"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add subordinate", "content_type": 3, "codename": "add_subordinate"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change subordinate", "content_type": 3, "codename": "change_subordinate"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete subordinate", "content_type": 3, "codename": "delete_subordinate"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view subordinate", "content_type": 3, "codename": "view_subordinate"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add log entry", "content_type": 4, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change log entry", "content_type": 4, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete log entry", "content_type": 4, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view log entry", "content_type": 4, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add permission", "content_type": 5, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change permission", "content_type": 5, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete permission", "content_type": 5, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view permission", "content_type": 5, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add group", "content_type": 6, "codename": "add_group"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change group", "content_type": 6, "codename": "change_group"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete group", "content_type": 6, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view group", "content_type": 6, "codename": "view_group"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add user", "content_type": 7, "codename": "add_user"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change user", "content_type": 7, "codename": "change_user"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete user", "content_type": 7, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view user", "content_type": 7, "codename": "view_user"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add content type", "content_type": 8, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change content type", "content_type": 8, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete content type", "content_type": 8, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view content type", "content_type": 8, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can add session", "content_type": 9, "codename": "add_session"}}, {"model": "auth.permission", "pk": 34, "fields": {"name": "Can change session", "content_type": 9, "codename": "change_session"}}, {"model": "auth.permission", "pk": 35, "fields": {"name": "Can delete session", "content_type": 9, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 36, "fields": {"name": "Can view session", "content_type": 9, "codename": "view_session"}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "trustmarks", "model": "trustmarktype"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "trustmarks", "model": "trustmark"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "entities", "model": "subordinate"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "sessions", "model": "session"}}] \ No newline at end of file +[{"model": "trustmarks.trustmarktype", "pk": 1, "fields": {"tmtype": "https://sunet.se/does_not_exist_trustmark", "autorenew": true, "valid_for": 8760, "renewal_time": 48, "active": true}}, {"model": "trustmarks.trustmarktype", "pk": 2, "fields": {"tmtype": "https://example.com/trust_mark_type", "autorenew": true, "valid_for": 720, "renewal_time": 48, "active": true}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add trust mark type", "content_type": 1, "codename": "add_trustmarktype"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change trust mark type", "content_type": 1, "codename": "change_trustmarktype"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete trust mark type", "content_type": 1, "codename": "delete_trustmarktype"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view trust mark type", "content_type": 1, "codename": "view_trustmarktype"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add trust mark", "content_type": 2, "codename": "add_trustmark"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change trust mark", "content_type": 2, "codename": "change_trustmark"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete trust mark", "content_type": 2, "codename": "delete_trustmark"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view trust mark", "content_type": 2, "codename": "view_trustmark"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add subordinate", "content_type": 3, "codename": "add_subordinate"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change subordinate", "content_type": 3, "codename": "change_subordinate"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete subordinate", "content_type": 3, "codename": "delete_subordinate"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view subordinate", "content_type": 3, "codename": "view_subordinate"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add log entry", "content_type": 4, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change log entry", "content_type": 4, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete log entry", "content_type": 4, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view log entry", "content_type": 4, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add permission", "content_type": 5, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change permission", "content_type": 5, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete permission", "content_type": 5, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view permission", "content_type": 5, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add group", "content_type": 6, "codename": "add_group"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change group", "content_type": 6, "codename": "change_group"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete group", "content_type": 6, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view group", "content_type": 6, "codename": "view_group"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add user", "content_type": 7, "codename": "add_user"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change user", "content_type": 7, "codename": "change_user"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete user", "content_type": 7, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view user", "content_type": 7, "codename": "view_user"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add content type", "content_type": 8, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change content type", "content_type": 8, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete content type", "content_type": 8, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view content type", "content_type": 8, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can add session", "content_type": 9, "codename": "add_session"}}, {"model": "auth.permission", "pk": 34, "fields": {"name": "Can change session", "content_type": 9, "codename": "change_session"}}, {"model": "auth.permission", "pk": 35, "fields": {"name": "Can delete session", "content_type": 9, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 36, "fields": {"name": "Can view session", "content_type": 9, "codename": "view_session"}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "trustmarks", "model": "trustmarktype"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "trustmarks", "model": "trustmark"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "entities", "model": "subordinate"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "sessions", "model": "session"}}] \ No newline at end of file diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 49e2083..0d27575 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -7,12 +7,13 @@ def test_trustmarktypes_list(db): # don't forget to import router from code above self = TestCase() + self.maxDiff = None trustmark_list = [ - {"tmtype": "https://sunet.se/does_not_exist_trustmark", "valid_for": 365}, - {"tmtype": "https://example.com/trust_mark", "valid_for": 365}, + {"tmtype": "https://sunet.se/does_not_exist_trustmark", "valid_for": 8760, 'active': True, 'autorenew': True,'renewal_time': 48,}, + {"tmtype": "https://example.com/trust_mark_type", "valid_for": 720, 'active': True, 'autorenew': True,'renewal_time': 48,}, ] client: TestClient = TestClient(router) - response = client.get("/trust_mark_type/list") + response = client.get("/trustmarktypes") self.assertEqual(response.status_code, 200) marks = response.json() From 6355aa97a30f7731c064b3604484fd2f57c96a3c Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 16:27:19 +0200 Subject: [PATCH 32/85] Reformats by ruff --- admin/trustmarks/migrations/0001_initial.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/admin/trustmarks/migrations/0001_initial.py b/admin/trustmarks/migrations/0001_initial.py index fad587a..193f51e 100644 --- a/admin/trustmarks/migrations/0001_initial.py +++ b/admin/trustmarks/migrations/0001_initial.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] @@ -31,11 +30,7 @@ class Migration(migrations.Migration): ("active", models.BooleanField(default=True)), ], options={ - "indexes": [ - models.Index( - fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx" - ) - ], + "indexes": [models.Index(fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx")], }, ), migrations.CreateModel( @@ -52,9 +47,7 @@ class Migration(migrations.Migration): ), ( "added", - models.DateTimeField( - db_default=django.db.models.functions.datetime.Now() - ), + models.DateTimeField(db_default=django.db.models.functions.datetime.Now()), ), ("domain", models.CharField()), ("active", models.BooleanField()), @@ -71,12 +64,8 @@ class Migration(migrations.Migration): ], options={ "indexes": [ - models.Index( - fields=["domain"], name="trustmarks__domain_101ea9_idx" - ), - models.Index( - fields=["active"], name="trustmarks__active_292a4b_idx" - ), + models.Index(fields=["domain"], name="trustmarks__domain_101ea9_idx"), + models.Index(fields=["active"], name="trustmarks__active_292a4b_idx"), models.Index(fields=["tmt"], name="trustmarks__tmt_id_4c0dfb_idx"), ], "unique_together": {("tmt", "domain")}, From 4e5bfac3f90ac9e70a6e4a86464a764f27628660 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 16:27:45 +0200 Subject: [PATCH 33/85] Updates test for list trustmarktypes --- admin/inmoradmin/api.py | 11 ++++++++++- admin/tests/test_api.py | 18 ++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 069b34c..7de35d1 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -21,6 +21,15 @@ class TrustMarkTypeSchema(Schema): active: bool = DEFAULTS["trustmarktype"]["active"] +class TrustMarkTypeOutSchema(Schema): + id: int + tmtype: str + autorenew: bool + valid_for: int + renewal_time: int + active: bool + + class Message(Schema): message: str id: int = 0 @@ -46,7 +55,7 @@ def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): return 500, {"message": "Error while creating a new TrustMarkType"} -@router.get("/trustmarktypes", response=list[TrustMarkTypeSchema]) +@router.get("/trustmarktypes", response=list[TrustMarkTypeOutSchema]) @paginate(LimitOffsetPagination) def list_trust_mark_type( request: HttpRequest, diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 0d27575..6b1f6d5 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -9,8 +9,22 @@ def test_trustmarktypes_list(db): self = TestCase() self.maxDiff = None trustmark_list = [ - {"tmtype": "https://sunet.se/does_not_exist_trustmark", "valid_for": 8760, 'active': True, 'autorenew': True,'renewal_time': 48,}, - {"tmtype": "https://example.com/trust_mark_type", "valid_for": 720, 'active': True, 'autorenew': True,'renewal_time': 48,}, + { + "tmtype": "https://sunet.se/does_not_exist_trustmark", + "id": 1, + "valid_for": 8760, + "active": True, + "autorenew": True, + "renewal_time": 48, + }, + { + "tmtype": "https://example.com/trust_mark_type", + "id": 2, + "valid_for": 720, + "active": True, + "autorenew": True, + "renewal_time": 48, + }, ] client: TestClient = TestClient(router) response = client.get("/trustmarktypes") From 0ccef923f26551e10d321ffbd65b9c87a42053b0 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 16:36:18 +0200 Subject: [PATCH 34/85] Adds test for create trustmarktype --- admin/tests/test_api.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 6b1f6d5..e83cac2 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -33,3 +33,21 @@ def test_trustmarktypes_list(db): marks = response.json() self.assertEqual(marks["count"], 2) self.assertEqual(marks["items"], trustmark_list) + + +def test_trustmarktypes_create(db): + # don't forget to import router from code above + self = TestCase() + self.maxDiff = None + data = { + "tmtype": "https://test.sunet.se/does_not_exist_trustmark", + "valid_for": 8760, + "active": True, + "autorenew": True, + "renewal_time": 48, + } + client: TestClient = TestClient(router) + response = client.post("/trustmarktypes", json=data) + + self.assertEqual(response.status_code, 201) + From f19e66e69a9525b5058c0413274a34a35da84180 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 20:10:44 +0200 Subject: [PATCH 35/85] Returns TrustMarkTypeOutScheme if create Also returns (403, Obj) in case object already exists. --- admin/inmoradmin/api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 7de35d1..4af657f 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -35,7 +35,10 @@ class Message(Schema): id: int = 0 -@router.post("/trustmarktypes", response={201: Message, 403: Message, 500: Message}) +@router.post( + "/trustmarktypes", + response={201: TrustMarkTypeOutSchema, 403: TrustMarkTypeOutSchema, 500: Message}, +) def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): """Creates a new trust_mark_type""" try: @@ -47,9 +50,9 @@ def create_trust_mark_type(request: HttpRequest, data: TrustMarkTypeSchema): active=data.active, ) if created: - return 201, {"message": "TrustMarkType created Succesfully.", "id": tmt.id} + return 201, tmt else: - return 403, {"message": "TrustMarkType already existed.", "id": tmt.id} + return 403, tmt except Exception as e: print(e) return 500, {"message": "Error while creating a new TrustMarkType"} From 5bca3276082d45b5857def47d20b8bb89a94e37b Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 4 Sep 2025 20:11:58 +0200 Subject: [PATCH 36/85] Adds TrustMarkType creation tests --- admin/tests/test_api.py | 65 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index e83cac2..88eebab 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -1,3 +1,4 @@ +import pytest from django.test import TestCase from ninja.testing import TestClient @@ -35,19 +36,69 @@ def test_trustmarktypes_list(db): self.assertEqual(marks["items"], trustmark_list) +@pytest.mark.django_db def test_trustmarktypes_create(db): # don't forget to import router from code above self = TestCase() self.maxDiff = None - data = { - "tmtype": "https://test.sunet.se/does_not_exist_trustmark", - "valid_for": 8760, - "active": True, - "autorenew": True, - "renewal_time": 48, - } + data = { + "tmtype": "https://test.sunet.se/does_not_exist_trustmark", + "valid_for": 8760, + "active": True, + "autorenew": True, + "renewal_time": 48, + } + client: TestClient = TestClient(router) + response = client.post("/trustmarktypes", json=data) + + self.assertEqual(response.status_code, 201) + resp = response.json() + for key in data: + self.assertEqual(data[key], resp.get(key)) + + +@pytest.mark.django_db +def test_trustmarktypes_create_default(db): + # don't forget to import router from code above + self = TestCase() + self.maxDiff = None + data = { + "tmtype": "https://test.sunet.se/does_not_exist_trustmark", + } client: TestClient = TestClient(router) + response = client.get("/trustmarktypes") + marks = response.json() + self.assertEqual(marks["count"], 2) response = client.post("/trustmarktypes", json=data) self.assertEqual(response.status_code, 201) + resp = response.json() + for key in data: + self.assertEqual(data[key], resp.get(key)) + + +@pytest.mark.django_db +def test_trustmarktypes_create_double(db): + "We will try to create same entry twice." + # don't forget to import router from code above + self = TestCase() + self.maxDiff = None + data = { + "tmtype": "https://test.sunet.se/does_not_exist_trustmark", + "valid_for": 8760, + "active": True, + "autorenew": True, + "renewal_time": 48, + } + client: TestClient = TestClient(router) + response = client.get("/trustmarktypes") + marks = response.json() + self.assertEqual(marks["count"], 2) + response = client.post("/trustmarktypes", json=data) + self.assertEqual(response.status_code, 201) + response = client.post("/trustmarktypes", json=data) + self.assertEqual(response.status_code, 403) + resp = response.json() + for key in data: + self.assertEqual(data[key], resp.get(key)) From 53d694ac001b12f09f25df67ff88f7a0316a5149 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Fri, 5 Sep 2025 08:12:35 +0200 Subject: [PATCH 37/85] Filters DepricationWarning in tests https://docs.pytest.org/en/stable/how-to/capture-warnings.html This warning is from pydantic, beyond our control. --- admin/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/admin/pyproject.toml b/admin/pyproject.toml index 54d4c91..dbabe53 100644 --- a/admin/pyproject.toml +++ b/admin/pyproject.toml @@ -95,3 +95,4 @@ testpaths = "tests" pythonpath = ["."] DJANGO_SETTINGS_MODULE = "inmoradmin.settings" env_files = [".env"] +filterwarnings = ["ignore::DeprecationWarning"] From 5376a3cf6754801516909026944b90f180b037c2 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Fri, 5 Sep 2025 08:13:48 +0200 Subject: [PATCH 38/85] Adds TrustMarkType update API Also adds initial test. --- admin/inmoradmin/api.py | 40 ++++++++++++++++++++++++++++++++++++++++ admin/tests/test_api.py | 24 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 4af657f..56813c0 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -29,6 +29,13 @@ class TrustMarkTypeOutSchema(Schema): renewal_time: int active: bool +class TrustMarkTypeUpdateSchema(Schema): + autorenew: bool| None = None + valid_for: int | None = None + renewal_time: int | None = None + active: bool | None = None + + class Message(Schema): message: str @@ -67,4 +74,37 @@ def list_trust_mark_type( return TrustMarkType.objects.all() +@router.put("/trustmarktypes/{int:tmtid}", response={200: TrustMarkTypeOutSchema, 404: Message, 500: Message}) +def update_trust_mark_type( + request: HttpRequest, + tmtid: int, + data: TrustMarkTypeUpdateSchema +): + """Updates TrustMarkType""" + try: + updated = False + tmt = TrustMarkType.objects.get(id=tmtid) + if not data.active is None: + tmt.active = data.active + updated = True + if not data.autorenew is None: + tmt.autorenew = data.autorenew + updated = True + if not data.valid_for is None: + tmt.valid_for = data.valid_for + updated = True + if not data.renewal_time is None: + tmt.renewal_time = data.renewal_time + updated = True + # Now save if only updated + if updated: + tmt.save() + return tmt + except TrustMarkType.DoesNotExist: + return 404, {"message": "TrustMarkType could not be found.", "id": tmtid} + except Exception as e: + print(e) + return 500, {"message": "Failed to update TrustMarkType.", "id": tmtid} + + api.add_router("", router) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 88eebab..0d3a548 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -102,3 +102,27 @@ def test_trustmarktypes_create_double(db): resp = response.json() for key in data: self.assertEqual(data[key], resp.get(key)) + + +@pytest.mark.django_db +def test_trustmarktypes_update(db): + """Updates the values of an existing TrustMarkType.""" + self = TestCase() + self.maxDiff = None + data = { + "active": False, + "autorenew": False, + "renewal_time": 4, + "valid_for": 100, + } + client: TestClient = TestClient(router) + response = client.put("/trustmarktypes/2", json=data) + mark = response.json() + print(mark) + for key in data: + self.assertEqual(data[key], mark.get(key)) + # Now the other values + self.assertEqual(2, mark.get("id")) + self.assertEqual("https://example.com/trust_mark_type",mark.get("tmtype")) + + From 9c81b2439bf994dbee5cff09466f746ad3c552d4 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Sun, 7 Sep 2025 12:24:02 +0200 Subject: [PATCH 39/85] Reformats by ruff --- admin/tests/test_api.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 0d3a548..bf7e08d 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -123,6 +123,4 @@ def test_trustmarktypes_update(db): self.assertEqual(data[key], mark.get(key)) # Now the other values self.assertEqual(2, mark.get("id")) - self.assertEqual("https://example.com/trust_mark_type",mark.get("tmtype")) - - + self.assertEqual("https://example.com/trust_mark_type", mark.get("tmtype")) From f3f80df1e037e06d30d97ddf2beb79885a895e58 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 05:24:15 +0200 Subject: [PATCH 40/85] Adds hget typing for redis --- admin/typings/django_redis/client/default.pyi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/admin/typings/django_redis/client/default.pyi b/admin/typings/django_redis/client/default.pyi index 4cd9a3b..6c90982 100644 --- a/admin/typings/django_redis/client/default.pyi +++ b/admin/typings/django_redis/client/default.pyi @@ -6,6 +6,7 @@ import builtins from collections import OrderedDict from collections.abc import Iterable, Iterator from typing import Any, Optional, Union + from django.core.cache.backends.base import BaseCache from redis import Redis from redis.typing import AbsExpiryT, EncodableT, ExpiryT, KeyT @@ -516,3 +517,14 @@ class DefaultClient: Return True if key exists in hash name, else False. """ ... + + def hget( + self, + name: str, + key: KeyT, + ) -> Any: + """ + Returns the value + """ + ... + From 13276f1229f759fc4f7450284b81e7d738c12971 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 05:26:53 +0200 Subject: [PATCH 41/85] Adds TrustMark create API --- admin/inmoradmin/api.py | 124 +++++++++++++++++++++++++++++++++++----- admin/tests/test_api.py | 27 ++++++++- admin/trustmarks/lib.py | 35 ++++++++++-- 3 files changed, 168 insertions(+), 18 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 56813c0..7ca433a 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -2,10 +2,13 @@ from django.conf import settings from django.http import HttpRequest +from django_redis import get_redis_connection from ninja import NinjaAPI, Router, Schema from ninja.pagination import LimitOffsetPagination, paginate +from redis.client import Redis -from trustmarks.models import TrustMarkType +from trustmarks.lib import add_trustmark, get_expiry, get_trustmark +from trustmarks.models import TrustMark, TrustMarkType api = NinjaAPI() router = Router() @@ -29,19 +32,42 @@ class TrustMarkTypeOutSchema(Schema): renewal_time: int active: bool + class TrustMarkTypeUpdateSchema(Schema): - autorenew: bool| None = None + autorenew: bool | None = None valid_for: int | None = None renewal_time: int | None = None active: bool | None = None +class TrustMarkSchema(Schema): + tmt: int + domain: str + autorenew: bool | None = None + valid_for: int | None = None + renewal_time: int | None = None + active: bool | None = None + + +class TrustMarkOutSchema(Schema): + id: int + domain: str + expire_at: float + autorenew: bool | None = None + valid_for: int | None = None + renewal_time: int | None = None + active: bool | None = None + mark: str | None = None + class Message(Schema): message: str id: int = 0 +# API for TrustMarkTypes + + @router.post( "/trustmarktypes", response={201: TrustMarkTypeOutSchema, 403: TrustMarkTypeOutSchema, 500: Message}, @@ -74,32 +100,31 @@ def list_trust_mark_type( return TrustMarkType.objects.all() -@router.put("/trustmarktypes/{int:tmtid}", response={200: TrustMarkTypeOutSchema, 404: Message, 500: Message}) -def update_trust_mark_type( - request: HttpRequest, - tmtid: int, - data: TrustMarkTypeUpdateSchema -): +@router.put( + "/trustmarktypes/{int:tmtid}", + response={200: TrustMarkTypeOutSchema, 404: Message, 500: Message}, +) +def update_trust_mark_type(request: HttpRequest, tmtid: int, data: TrustMarkTypeUpdateSchema): """Updates TrustMarkType""" try: updated = False tmt = TrustMarkType.objects.get(id=tmtid) - if not data.active is None: + if data.active is not None: tmt.active = data.active updated = True - if not data.autorenew is None: + if data.autorenew is not None: tmt.autorenew = data.autorenew updated = True - if not data.valid_for is None: + if data.valid_for is not None: tmt.valid_for = data.valid_for updated = True - if not data.renewal_time is None: + if data.renewal_time is not None: tmt.renewal_time = data.renewal_time updated = True # Now save if only updated if updated: tmt.save() - return tmt + return tmt except TrustMarkType.DoesNotExist: return 404, {"message": "TrustMarkType could not be found.", "id": tmtid} except Exception as e: @@ -107,4 +132,77 @@ def update_trust_mark_type( return 500, {"message": "Failed to update TrustMarkType.", "id": tmtid} +# API for TrustMarks + + +@router.post( + "/trustmarks", + response={201: TrustMarkOutSchema, 403: TrustMarkOutSchema, 404: Message, 500: Message}, +) +def create_trust_mark(request: HttpRequest, data: TrustMarkSchema): + """Creates a new TrustMarak for given domain nd TrustMarkType ID.""" + # First get the TrustMarkType + try: + tmt = TrustMarkType.objects.get(id=data.tmt) + except TrustMarkType.DoesNotExist: + return 404, {"message": "TrustMarkType could not be found.", "id": data.tmt} + + # Now fill in with defaults from TrustMarkType if not given. + if data.autorenew is None: + data.autorenew = tmt.autorenew + if data.active is None: + data.active = tmt.active + # valid_for and renewal_time can not be greater than TrustMarkType default. + if data.valid_for is None: + data.valid_for = tmt.valid_for + else: + # Make sure it is not greater + if data.valid_for > tmt.valid_for: + # Oops, we can not allow that. + return 400, { + "message": "valid_for is greater than allowed for the given TrustMarkType.", + "id": data.tmt, + } + # All good if we reach here. + if data.renewal_time is None: + data.renewal_time = tmt.renewal_time + else: + # Make sure it is not greater + if data.renewal_time > tmt.renewal_time: + # Oops, we can not allow that. + return 400, { + "message": "renewal_time is greater than allowed for the given TrustMarkType.", + "id": data.tmt, + } + try: + tm, created = TrustMark.objects.get_or_create( + tmt=tmt, + domain=data.domain, + autorenew=data.autorenew, + valid_for=data.valid_for, + renewal_time=data.renewal_time, + active=data.active, + ) + con: Redis = get_redis_connection("default") + if created: + # Now we should create the signed JWT and store in redis + + mark = add_trustmark(tm.domain, tmt.tmtype, tm.valid_for, con) + # Adds the newly created JWT in the response + setattr(tm, "mark", mark) + setattr(tm, "expire_at", get_expiry(mark)) + return 201, tm + else: + mark = get_trustmark(tm.domain, tmt.tmtype, con) + if isinstance(mark, str): + setattr(tm, "mark", mark) + setattr(tm, "expire_at", get_expiry(mark)) + return 403, tm + else: + return 500, {"message": "TrustMark already exists but could not be fetched."} + except Exception as e: + print(e) + return 500, {"message": "Error while creating a new TrustMark."} + + api.add_router("", router) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index bf7e08d..6cc254c 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -1,10 +1,18 @@ import pytest from django.test import TestCase +from jwcrypto import jwt +from jwcrypto.common import json_decode from ninja.testing import TestClient from inmoradmin.api import router +def get_payload(token_str: str): + "Helper method to get payload" + jose = jwt.JWT.from_jose_token(token_str) + return json_decode(jose.token.objects.get("payload", "")) + + def test_trustmarktypes_list(db): # don't forget to import router from code above self = TestCase() @@ -118,9 +126,26 @@ def test_trustmarktypes_update(db): client: TestClient = TestClient(router) response = client.put("/trustmarktypes/2", json=data) mark = response.json() - print(mark) for key in data: self.assertEqual(data[key], mark.get(key)) # Now the other values self.assertEqual(2, mark.get("id")) self.assertEqual("https://example.com/trust_mark_type", mark.get("tmtype")) + + +@pytest.mark.django_db +def test_trustmark_create(db): + domain = "https://fakerp0.labb.sunet.se" + + self = TestCase() + self.maxDiff = None + data = {"tmt": 2, "domain": domain} + client: TestClient = TestClient(router) + response = client.post("/trustmarks", json=data) + + self.assertEqual(response.status_code, 201) + resp = response.json() + jwt_token = resp["mark"] + payload = get_payload(jwt_token) + self.assertEqual(domain, payload.get("sub")) + self.assertEqual("https://example.com/trust_mark_type", payload.get("trust_mark_type")) diff --git a/admin/trustmarks/lib.py b/admin/trustmarks/lib.py index a0160e6..12d09fa 100644 --- a/admin/trustmarks/lib.py +++ b/admin/trustmarks/lib.py @@ -1,11 +1,11 @@ from datetime import datetime, timedelta +import redis from django.conf import settings from jwcrypto import jwt +from jwcrypto.common import json_decode from pydantic import BaseModel -import redis - class TrustMarkRequest(BaseModel): entity: str @@ -17,9 +17,14 @@ class TrustMarkTypeRequest(BaseModel): def add_trustmark(entity: str, trustmarktype: str, expiry: int, r: redis.Redis) -> str: - """Adds a new subordinate to the federation. + """Adds a new TrustMark for a given entity for a given TrustMarkType. - :args entity_id: The entity_id to be added + :args entity: The entity_id to be added + :args trustmarktype: The TrustMarkType value in JWT + :args expiry: The JWT will be valid for the number of hours. + :args r: Redis client instance. + + :returns: JWT as str. """ # Based on https://openid.net/specs/openid-federation-1_0.html#name-trust-marks @@ -45,3 +50,25 @@ def add_trustmark(entity: str, trustmarktype: str, expiry: int, r: redis.Redis) # second, add to the set of trust_mark_type _ = r.sadd(f"inmor:tmtype:{trustmarktype}", entity) return token_data + + +def get_trustmark(entity: str, trustmarktype: str, r: redis.Redis) -> str | None: + """Get a TrustMark for an entity from redis. + + :args entity: The entity_id to be added + :args trustmarktype: The TrustMarkType value in JWT + :args r: Redis client instance. + + :returns: JWT as str. + """ + token = r.hget(f"inmor:tm:{entity}", trustmarktype) + if isinstance(token, bytes): + return token.decode("utf-8") + return + + +def get_expiry(token_str: str) -> float: + """Extracts the expiry time as timestamp from JWT.""" + jose = jwt.JWT.from_jose_token(token_str) + data = json_decode(jose.token.objects.get("payload", "")) + return data.get("exp") From 5ad9e5443bfaf0ca79e50a9cf7c68933eb64239a Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 05:27:17 +0200 Subject: [PATCH 42/85] Updates ty --- admin/uv.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/admin/uv.lock b/admin/uv.lock index 24343d5..a1ffaed 100644 --- a/admin/uv.lock +++ b/admin/uv.lock @@ -806,27 +806,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.1a19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/04/281c1a3c9c53dae5826b9d01a3412de653e3caf1ca50ce1265da66e06d73/ty-0.0.1a19.tar.gz", hash = "sha256:894f6a13a43989c8ef891ae079b3b60a0c0eae00244abbfbbe498a3840a235ac", size = 4098412, upload-time = "2025-08-19T13:29:58.559Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/65/a61cfcc7248b0257a3110bf98d3d910a4729c1063abdbfdcd1cad9012323/ty-0.0.1a19-py3-none-linux_armv6l.whl", hash = "sha256:e0e7762f040f4bab1b37c57cb1b43cc3bc5afb703fa5d916dfcafa2ef885190e", size = 8143744, upload-time = "2025-08-19T13:29:13.88Z" }, - { url = "https://files.pythonhosted.org/packages/02/d9/232afef97d9afa2274d23a4c49a3ad690282ca9696e1b6bbb6e4e9a1b072/ty-0.0.1a19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cd0a67ac875f49f34d9a0b42dcabf4724194558a5dd36867209d5695c67768f7", size = 8305799, upload-time = "2025-08-19T13:29:17.322Z" }, - { url = "https://files.pythonhosted.org/packages/20/14/099d268da7a9cccc6ba38dfc124f6742a1d669bc91f2c61a3465672b4f71/ty-0.0.1a19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ff8b1c0b85137333c39eccd96c42603af8ba7234d6e2ed0877f66a4a26750dd4", size = 7901431, upload-time = "2025-08-19T13:29:21.635Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cd/3f1ca6e1d7f77cc4d08910a3fc4826313c031c0aae72286ae859e737670c/ty-0.0.1a19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fef34a29f4b97d78aa30e60adbbb12137cf52b8b2b0f1a408dd0feb0466908a", size = 8051501, upload-time = "2025-08-19T13:29:23.741Z" }, - { url = "https://files.pythonhosted.org/packages/47/72/ddbec39f48ce3f5f6a3fa1f905c8fff2873e59d2030f738814032bd783e3/ty-0.0.1a19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0f219cb43c0c50fc1091f8ebd5548d3ef31ee57866517b9521d5174978af9fd", size = 7981234, upload-time = "2025-08-19T13:29:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/f2/0f/58e76b8d4634df066c790d362e8e73b25852279cd6f817f099b42a555a66/ty-0.0.1a19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22abb6c1f14c65c1a2fafd38e25dd3c87994b3ab88cb0b323235b51dbad082d9", size = 8916394, upload-time = "2025-08-19T13:29:27.932Z" }, - { url = "https://files.pythonhosted.org/packages/70/30/01bfd93ccde11540b503e2539e55f6a1fc6e12433a229191e248946eb753/ty-0.0.1a19-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5b49225c349a3866e38dd297cb023a92d084aec0e895ed30ca124704bff600e6", size = 9412024, upload-time = "2025-08-19T13:29:30.942Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a2/2216d752f5f22c5c0995f9b13f18337301220f2a7d952c972b33e6a63583/ty-0.0.1a19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88f41728b3b07402e0861e3c34412ca963268e55f6ab1690208f25d37cb9d63c", size = 9032657, upload-time = "2025-08-19T13:29:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/24/c7/e6650b0569be1b69a03869503d07420c9fb3e90c9109b09726c44366ce63/ty-0.0.1a19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33814a1197ec3e930fcfba6fb80969fe7353957087b42b88059f27a173f7510b", size = 8812775, upload-time = "2025-08-19T13:29:36.505Z" }, - { url = "https://files.pythonhosted.org/packages/35/c6/b8a20e06b97fe8203059d56d8f91cec4f9633e7ba65f413d80f16aa0be04/ty-0.0.1a19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71b7f2b674a287258f628acafeecd87691b169522945ff6192cd8a69af15857", size = 8631417, upload-time = "2025-08-19T13:29:38.837Z" }, - { url = "https://files.pythonhosted.org/packages/be/99/821ca1581dcf3d58ffb7bbe1cde7e1644dbdf53db34603a16a459a0b302c/ty-0.0.1a19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a7f8ef9ac4c38e8651c18c7380649c5a3fa9adb1a6012c721c11f4bbdc0ce24", size = 7928900, upload-time = "2025-08-19T13:29:41.08Z" }, - { url = "https://files.pythonhosted.org/packages/08/cb/59f74a0522e57565fef99e2287b2bc803ee47ff7dac250af26960636939f/ty-0.0.1a19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:60f40e72f0fbf4e54aa83d9a6cb1959f551f83de73af96abbb94711c1546bd60", size = 8003310, upload-time = "2025-08-19T13:29:43.165Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b3/1209b9acb5af00a2755114042e48fb0f71decc20d9d77a987bf5b3d1a102/ty-0.0.1a19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:64971e4d3e3f83dc79deb606cc438255146cab1ab74f783f7507f49f9346d89d", size = 8496463, upload-time = "2025-08-19T13:29:46.136Z" }, - { url = "https://files.pythonhosted.org/packages/a2/d6/a4b6ba552d347a08196d83a4d60cb23460404a053dd3596e23a922bce544/ty-0.0.1a19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9aadbff487e2e1486e83543b4f4c2165557f17432369f419be9ba48dc47625ca", size = 8700633, upload-time = "2025-08-19T13:29:49.351Z" }, - { url = "https://files.pythonhosted.org/packages/96/c5/258f318d68b95685c8d98fb654a38882c9d01ce5d9426bed06124f690f04/ty-0.0.1a19-py3-none-win32.whl", hash = "sha256:00b75b446357ee22bcdeb837cb019dc3bc1dc5e5013ff0f46a22dfe6ce498fe2", size = 7811441, upload-time = "2025-08-19T13:29:52.077Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bb/039227eee3c0c0cddc25f45031eea0f7f10440713f12d333f2f29cf8e934/ty-0.0.1a19-py3-none-win_amd64.whl", hash = "sha256:aaef76b2f44f6379c47adfe58286f0c56041cb2e374fd8462ae8368788634469", size = 8441186, upload-time = "2025-08-19T13:29:54.53Z" }, - { url = "https://files.pythonhosted.org/packages/74/5f/bceb29009670ae6f759340f9cb434121bc5ed84ad0f07bdc6179eaaa3204/ty-0.0.1a19-py3-none-win_arm64.whl", hash = "sha256:893755bb35f30653deb28865707e3b16907375c830546def2741f6ff9a764710", size = 8000810, upload-time = "2025-08-19T13:29:56.796Z" }, +version = "0.0.1a20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/82/a5e3b4bc5280ec49c4b0b43d0ff727d58c7df128752c9c6f97ad0b5f575f/ty-0.0.1a20.tar.gz", hash = "sha256:933b65a152f277aa0e23ba9027e5df2c2cc09e18293e87f2a918658634db5f15", size = 4194773, upload-time = "2025-09-03T12:35:46.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/c8/f7d39392043d5c04936f6cad90e50eb661965ed092ca4bfc01db917d7b8a/ty-0.0.1a20-py3-none-linux_armv6l.whl", hash = "sha256:f73a7aca1f0d38af4d6999b375eb00553f3bfcba102ae976756cc142e14f3450", size = 8443599, upload-time = "2025-09-03T12:35:04.289Z" }, + { url = "https://files.pythonhosted.org/packages/1e/57/5aec78f9b8a677b7439ccded7d66c3361e61247e0f6b14e659b00dd01008/ty-0.0.1a20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cad12c857ea4b97bf61e02f6796e13061ccca5e41f054cbd657862d80aa43bae", size = 8618102, upload-time = "2025-09-03T12:35:07.448Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/50c9107d93cdb55676473d9dc4e2339af6af606660c9428d3b86a1b2a476/ty-0.0.1a20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f153b65c7fcb6b8b59547ddb6353761b3e8d8bb6f0edd15e3e3ac14405949f7a", size = 8192167, upload-time = "2025-09-03T12:35:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/85/28/018b2f330109cee19e81c5ca9df3dc29f06c5778440eb9af05d4550c4302/ty-0.0.1a20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8c4336987a6a781d4392a9fd7b3a39edb7e4f3dd4f860e03f46c932b52aefa2", size = 8349256, upload-time = "2025-09-03T12:35:11.76Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c9/2f8797a05587158f52b142278796ffd72c893bc5ad41840fce5aeb65c6f2/ty-0.0.1a20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ff75cd4c744d09914e8c9db8d99e02f82c9379ad56b0a3fc4c5c9c923cfa84e", size = 8271214, upload-time = "2025-09-03T12:35:13.741Z" }, + { url = "https://files.pythonhosted.org/packages/30/d4/2cac5e5eb9ee51941358cb3139aadadb59520cfaec94e4fcd2b166969748/ty-0.0.1a20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e26437772be7f7808868701f2bf9e14e706a6ec4c7d02dbd377ff94d7ba60c11", size = 9264939, upload-time = "2025-09-03T12:35:16.896Z" }, + { url = "https://files.pythonhosted.org/packages/93/96/a6f2b54e484b2c6a5488f217882237dbdf10f0fdbdb6cd31333d57afe494/ty-0.0.1a20-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:83a7ee12465841619b5eb3ca962ffc7d576bb1c1ac812638681aee241acbfbbe", size = 9743137, upload-time = "2025-09-03T12:35:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/95b40dcbec3d222f3af5fe5dd1ce066d42f8a25a2f70d5724490457048e7/ty-0.0.1a20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:726d0738be4459ac7ffae312ba96c5f486d6cbc082723f322555d7cba9397871", size = 9368153, upload-time = "2025-09-03T12:35:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/2c/24/689fa4c4270b9ef9a53dc2b1d6ffade259ba2c4127e451f0629e130ea46a/ty-0.0.1a20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b481f26513f38543df514189fb16744690bcba8d23afee95a01927d93b46e36", size = 9099637, upload-time = "2025-09-03T12:35:24.94Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5b/913011cbf3ea4030097fb3c4ce751856114c9e1a5e1075561a4c5242af9b/ty-0.0.1a20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7abbe3c02218c12228b1d7c5f98c57240029cc3bcb15b6997b707c19be3908c1", size = 8952000, upload-time = "2025-09-03T12:35:27.288Z" }, + { url = "https://files.pythonhosted.org/packages/df/f9/f5ba2ae455b20c5bb003f9940ef8142a8c4ed9e27de16e8f7472013609db/ty-0.0.1a20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fff51c75ee3f7cc6d7722f2f15789ef8ffe6fd2af70e7269ac785763c906688e", size = 8217938, upload-time = "2025-09-03T12:35:29.54Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/17002cf9032f0981cdb8c898d02422c095c30eefd69ca62a8b705d15bd0f/ty-0.0.1a20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b4124ab75e0e6f09fe7bc9df4a77ee43c5e0ef7e61b0c149d7c089d971437cbd", size = 8292369, upload-time = "2025-09-03T12:35:31.748Z" }, + { url = "https://files.pythonhosted.org/packages/28/d6/0879b1fb66afe1d01d45c7658f3849aa641ac4ea10679404094f3b40053e/ty-0.0.1a20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8a138fa4f74e6ed34e9fd14652d132409700c7ff57682c2fed656109ebfba42f", size = 8811973, upload-time = "2025-09-03T12:35:33.997Z" }, + { url = "https://files.pythonhosted.org/packages/60/1e/70bf0348cfe8ba5f7532983f53c508c293ddf5fa9f942ed79a3c4d576df3/ty-0.0.1a20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8eff8871d6b88d150e2a67beba2c57048f20c090c219f38ed02eebaada04c124", size = 9010990, upload-time = "2025-09-03T12:35:36.766Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ca/03d85c7650359247b1ca3f38a0d869a608ef540450151920e7014ed58292/ty-0.0.1a20-py3-none-win32.whl", hash = "sha256:3c2ace3a22fab4bd79f84c74e3dab26e798bfba7006bea4008d6321c1bd6efc6", size = 8100746, upload-time = "2025-09-03T12:35:40.007Z" }, + { url = "https://files.pythonhosted.org/packages/94/53/7a1937b8c7a66d0c8ed7493de49ed454a850396fe137d2ae12ed247e0b2f/ty-0.0.1a20-py3-none-win_amd64.whl", hash = "sha256:f41e77ff118da3385915e13c3f366b3a2f823461de54abd2e0ca72b170ba0f19", size = 8748861, upload-time = "2025-09-03T12:35:42.175Z" }, + { url = "https://files.pythonhosted.org/packages/27/36/5a3a70c5d497d3332f9e63cabc9c6f13484783b832fecc393f4f1c0c4aa8/ty-0.0.1a20-py3-none-win_arm64.whl", hash = "sha256:d8ac1c5a14cda5fad1a8b53959d9a5d979fe16ce1cc2785ea8676fed143ac85f", size = 8269906, upload-time = "2025-09-03T12:35:45.045Z" }, ] [[package]] From 3aedef95b9d90b9325affc3322ae3797683de31e Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 06:04:50 +0200 Subject: [PATCH 43/85] Adds test for double TrustMark We try to create the same TrustMark twice. --- admin/tests/test_api.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 6cc254c..395ec51 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -149,3 +149,26 @@ def test_trustmark_create(db): payload = get_payload(jwt_token) self.assertEqual(domain, payload.get("sub")) self.assertEqual("https://example.com/trust_mark_type", payload.get("trust_mark_type")) + +@pytest.mark.django_db +def test_trustmark_create_twice(db): + domain = "https://fakerp0.labb.sunet.se" + + self = TestCase() + self.maxDiff = None + data = {"tmt": 2, "domain": domain} + client: TestClient = TestClient(router) + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 201) + resp = response.json() + jwt_token = resp["mark"] + payload = get_payload(jwt_token) + self.assertEqual(domain, payload.get("sub")) + self.assertEqual("https://example.com/trust_mark_type", payload.get("trust_mark_type")) + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 403) + resp = response.json() + jwt_token = resp["mark"] + payload = get_payload(jwt_token) + self.assertEqual(domain, payload.get("sub")) + self.assertEqual("https://example.com/trust_mark_type", payload.get("trust_mark_type")) From 2ab513f35a8ef9d5fafdd955804f7f7a2a9da2c6 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 06:46:45 +0200 Subject: [PATCH 44/85] Adds API endpoint to GET TrustMarkType --- admin/inmoradmin/api.py | 36 ++++++++++++++++++++++++++++++++++++ admin/tests/test_api.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 7ca433a..056abf0 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -16,6 +16,10 @@ DEFAULTS: dict[str, dict[str, Any]] = settings.TA_DEFAULTS +class TrustMarkTypeGetSchema(Schema): + tmtype: str + + class TrustMarkTypeSchema(Schema): tmtype: str autorenew: bool = DEFAULTS["trustmarktype"]["autorenew"] @@ -100,6 +104,38 @@ def list_trust_mark_type( return TrustMarkType.objects.all() +@router.get( + "/trustmarktypes/{int:tmtid}", + response={200: TrustMarkTypeOutSchema, 404: Message, 500: Message}, +) +def get_trustmarktype_byid(request: HttpRequest, tmtid: int): + """Gets a TrustMarkType""" + try: + tmt = TrustMarkType.objects.get(id=tmtid) + return tmt + except TrustMarkType.DoesNotExist: + return 404, {"message": "TrustMarkType could not be found.", "id": tmtid} + except Exception as e: + print(e) + return 500, {"message": "Failed to get TrustMarkType.", "id": tmtid} + + +@router.get( + "/trustmarktypes/", + response={200: TrustMarkTypeOutSchema, 404: Message, 500: Message}, +) +def get_trustmarktype_bytype(request: HttpRequest, data: TrustMarkTypeGetSchema): + """Gets a TrustMarkType""" + try: + tmt = TrustMarkType.objects.get(tmtype=data.tmtype) + return tmt + except TrustMarkType.DoesNotExist: + return 404, {"message": "TrustMarkType could not be found."} + except Exception as e: + print(e) + return 500, {"message": "Failed to get TrustMarkType."} + + @router.put( "/trustmarktypes/{int:tmtid}", response={200: TrustMarkTypeOutSchema, 404: Message, 500: Message}, diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 395ec51..bc42e49 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -44,6 +44,45 @@ def test_trustmarktypes_list(db): self.assertEqual(marks["items"], trustmark_list) +def test_trustmarktypes_get_byid(db): + # don't forget to import router from code above + self = TestCase() + self.maxDiff = None + data = { + "tmtype": "https://example.com/trust_mark_type", + "id": 2, + "valid_for": 720, + "active": True, + "autorenew": True, + "renewal_time": 48, + } + client: TestClient = TestClient(router) + response = client.get("/trustmarktypes/2") + self.assertEqual(response.status_code, 200) + mark = response.json() + self.assertEqual(mark, data) + + +def test_trustmarktypes_get_bytype(db): + # don't forget to import router from code above + self = TestCase() + self.maxDiff = None + data = { + "tmtype": "https://example.com/trust_mark_type", + "id": 2, + "valid_for": 720, + "active": True, + "autorenew": True, + "renewal_time": 48, + } + client: TestClient = TestClient(router) + response = client.get("/trustmarktypes/", json={"tmtype": "https://example.com/trust_mark_type"}) + self.assertEqual(response.status_code, 200) + mark = response.json() + self.assertEqual(mark, data) + + + @pytest.mark.django_db def test_trustmarktypes_create(db): # don't forget to import router from code above @@ -150,6 +189,7 @@ def test_trustmark_create(db): self.assertEqual(domain, payload.get("sub")) self.assertEqual("https://example.com/trust_mark_type", payload.get("trust_mark_type")) + @pytest.mark.django_db def test_trustmark_create_twice(db): domain = "https://fakerp0.labb.sunet.se" From b03eb17971e732fc5fa2ec051f11078b8fea2572 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 07:29:10 +0200 Subject: [PATCH 45/85] Reformats by ruff --- admin/tests/test_api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index bc42e49..5c24c50 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -76,13 +76,14 @@ def test_trustmarktypes_get_bytype(db): "renewal_time": 48, } client: TestClient = TestClient(router) - response = client.get("/trustmarktypes/", json={"tmtype": "https://example.com/trust_mark_type"}) + response = client.get( + "/trustmarktypes/", json={"tmtype": "https://example.com/trust_mark_type"} + ) self.assertEqual(response.status_code, 200) mark = response.json() self.assertEqual(mark, data) - @pytest.mark.django_db def test_trustmarktypes_create(db): # don't forget to import router from code above From 1753f0ead5d51e4fde77a214feb35c66a613e023 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 14:00:35 +0200 Subject: [PATCH 46/85] Uses redis for test different than local --- admin/inmoradmin/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/inmoradmin/settings.py b/admin/inmoradmin/settings.py index ffa44c5..a90079f 100644 --- a/admin/inmoradmin/settings.py +++ b/admin/inmoradmin/settings.py @@ -134,10 +134,12 @@ LOGIN_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/" +REDIS_LOCATION: str = os.environ.get("REDIS_LOCATION", "redis://redis:6379/0") + CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://redis:6379/0", + "LOCATION": REDIS_LOCATION, "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", }, From 28578dc6387fda19c1e9e69850d8817273da8717 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 14:01:29 +0200 Subject: [PATCH 47/85] Adds redis dependency for tests --- admin/tests/conftest.py | 19 ++++++++++++++++++- admin/tests/test_api.py | 4 ++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/admin/tests/conftest.py b/admin/tests/conftest.py index 2fc1083..21c76af 100644 --- a/admin/tests/conftest.py +++ b/admin/tests/conftest.py @@ -4,9 +4,15 @@ import pytest from django.core.management import call_command from dotenv import load_dotenv +from pytest_redis import factories +from redis.client import Redis -load_dotenv() +trdb = factories.redis_proc() +rdb = factories.redisdb("trdb") + + +_ = load_dotenv() sys.path.append(os.path.dirname(os.path.dirname(__file__))) @@ -14,3 +20,14 @@ def db(request, django_db_setup, django_db_blocker): django_db_blocker.unblock() call_command("loaddata", "db.json") + + +@pytest.fixture(scope="function") +def loadredis(rdb: Redis) -> Redis: + """Loads the test data into redis instance for testing.""" + redis = rdb + # with open(os.path.join(dbpath, "dump.data"), "rb") as f: + # data = f.read() + # # Now redis-cli against this + # _ = subprocess.run(["redis-cli", "-p", "6088", "--pipe"], input=data) + return redis diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 5c24c50..151fe75 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -174,7 +174,7 @@ def test_trustmarktypes_update(db): @pytest.mark.django_db -def test_trustmark_create(db): +def test_trustmark_create(db, trdb): domain = "https://fakerp0.labb.sunet.se" self = TestCase() @@ -192,7 +192,7 @@ def test_trustmark_create(db): @pytest.mark.django_db -def test_trustmark_create_twice(db): +def test_trustmark_create_twice(db, trdb): domain = "https://fakerp0.labb.sunet.se" self = TestCase() From af2b633051609174e0ab79bd75e98773d413abac Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 14:02:58 +0200 Subject: [PATCH 48/85] Sets redis location for tests --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 04be42f..e71dab0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,3 +61,4 @@ jobs: # If we were running the job on in a container this would be postgres DB_HOST: localhost DB_PORT: ${{ job.services.postgres.ports[5432] }} # get randomly assigned published port + REDIS_LOCATION: "redis://localhost:6379/0" From 3093904d46f323fe391f0ac845f482733a028e97 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 9 Sep 2025 14:19:15 +0200 Subject: [PATCH 49/85] Uses 6088 for redis for admin tests --- .github/workflows/tests.yml | 2 +- admin/tests/conftest.py | 2 +- admin/tests/test_api.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e71dab0..3762ada 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,4 +61,4 @@ jobs: # If we were running the job on in a container this would be postgres DB_HOST: localhost DB_PORT: ${{ job.services.postgres.ports[5432] }} # get randomly assigned published port - REDIS_LOCATION: "redis://localhost:6379/0" + REDIS_LOCATION: "redis://localhost:6088/0" diff --git a/admin/tests/conftest.py b/admin/tests/conftest.py index 21c76af..471f7cd 100644 --- a/admin/tests/conftest.py +++ b/admin/tests/conftest.py @@ -7,7 +7,7 @@ from pytest_redis import factories from redis.client import Redis -trdb = factories.redis_proc() +trdb = factories.redis_proc(port=6088) rdb = factories.redisdb("trdb") diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 151fe75..bd739d4 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -174,7 +174,7 @@ def test_trustmarktypes_update(db): @pytest.mark.django_db -def test_trustmark_create(db, trdb): +def test_trustmark_create(db, loadredis): domain = "https://fakerp0.labb.sunet.se" self = TestCase() @@ -192,7 +192,7 @@ def test_trustmark_create(db, trdb): @pytest.mark.django_db -def test_trustmark_create_twice(db, trdb): +def test_trustmark_create_twice(db, loadredis): domain = "https://fakerp0.labb.sunet.se" self = TestCase() From 3856f671fba85eff846432e8b8783b726a3e739f Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 15 Sep 2025 13:31:34 +0200 Subject: [PATCH 50/85] Adds new columns to trustmark table --- admin/trustmarks/migrations/0001_initial.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/trustmarks/migrations/0001_initial.py b/admin/trustmarks/migrations/0001_initial.py index 193f51e..eec8f44 100644 --- a/admin/trustmarks/migrations/0001_initial.py +++ b/admin/trustmarks/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.6 on 2025-09-04 13:10 +# Generated by Django 5.2.6 on 2025-09-14 05:52 import django.db.models.deletion import django.db.models.functions.datetime @@ -54,6 +54,8 @@ class Migration(migrations.Migration): ("autorenew", models.BooleanField()), ("valid_for", models.IntegerField()), ("renewal_time", models.IntegerField()), + ("mark", models.CharField()), + ("expire_at", models.DateTimeField()), ( "tmt", models.ForeignKey( From a7c9fdc0514feb6b05eda66786eec7be43a201ae Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 15 Sep 2025 14:02:16 +0200 Subject: [PATCH 51/85] Updates trustmark model for null --- admin/trustmarks/migrations/0001_initial.py | 28 +++++++++++++++------ admin/trustmarks/models.py | 3 +++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/admin/trustmarks/migrations/0001_initial.py b/admin/trustmarks/migrations/0001_initial.py index eec8f44..0cf1bf8 100644 --- a/admin/trustmarks/migrations/0001_initial.py +++ b/admin/trustmarks/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.6 on 2025-09-14 05:52 +# Generated by Django 5.2.6 on 2025-09-15 11:45 import django.db.models.deletion import django.db.models.functions.datetime @@ -6,6 +6,7 @@ class Migration(migrations.Migration): + initial = True dependencies = [] @@ -30,7 +31,11 @@ class Migration(migrations.Migration): ("active", models.BooleanField(default=True)), ], options={ - "indexes": [models.Index(fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx")], + "indexes": [ + models.Index( + fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx" + ) + ], }, ), migrations.CreateModel( @@ -47,15 +52,17 @@ class Migration(migrations.Migration): ), ( "added", - models.DateTimeField(db_default=django.db.models.functions.datetime.Now()), + models.DateTimeField( + db_default=django.db.models.functions.datetime.Now() + ), ), ("domain", models.CharField()), ("active", models.BooleanField()), ("autorenew", models.BooleanField()), ("valid_for", models.IntegerField()), ("renewal_time", models.IntegerField()), - ("mark", models.CharField()), - ("expire_at", models.DateTimeField()), + ("mark", models.CharField(null=True)), + ("expire_at", models.DateTimeField(null=True)), ( "tmt", models.ForeignKey( @@ -66,9 +73,16 @@ class Migration(migrations.Migration): ], options={ "indexes": [ - models.Index(fields=["domain"], name="trustmarks__domain_101ea9_idx"), - models.Index(fields=["active"], name="trustmarks__active_292a4b_idx"), + models.Index( + fields=["domain"], name="trustmarks__domain_101ea9_idx" + ), + models.Index( + fields=["active"], name="trustmarks__active_292a4b_idx" + ), models.Index(fields=["tmt"], name="trustmarks__tmt_id_4c0dfb_idx"), + models.Index( + fields=["expire_at"], name="trustmarks__expire__f152b2_idx" + ), ], "unique_together": {("tmt", "domain")}, }, diff --git a/admin/trustmarks/models.py b/admin/trustmarks/models.py index 9fc8cc7..c2ae277 100644 --- a/admin/trustmarks/models.py +++ b/admin/trustmarks/models.py @@ -29,6 +29,8 @@ class TrustMark(models.Model): autorenew = models.BooleanField() valid_for = models.IntegerField() renewal_time = models.IntegerField() + mark = models.CharField(null=True) + expire_at = models.DateTimeField(null=True) def __str__(self): return self.domain @@ -39,4 +41,5 @@ class Meta: models.Index(fields=["domain"]), models.Index(fields=["active"]), models.Index(fields=["tmt"]), + models.Index(fields=["expire_at"]), ] From a407902f464af8051a4ad2add6a574d80d3e8e0b Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 15 Sep 2025 14:03:03 +0200 Subject: [PATCH 52/85] Adds redis is admin container Helps to run test cases. --- admin/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/Dockerfile b/admin/Dockerfile index c91f5ef..ec63ede 100644 --- a/admin/Dockerfile +++ b/admin/Dockerfile @@ -1,5 +1,5 @@ FROM python:3.13 -RUN apt update && apt install xmlsec1 -y +RUN apt update && apt install xmlsec1 redis -y ENV PYTHONUNBUFFERED=1 WORKDIR /code #RUN bash .docker/scripts/setup-sass.sh From fd6edb483ceeeda1cc2b061f5201fc7c20a31323 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 15 Sep 2025 14:03:43 +0200 Subject: [PATCH 53/85] Creates TrustMark via api --- admin/inmoradmin/api.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 056abf0..d8e4b1e 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import Any from django.conf import settings @@ -7,7 +8,7 @@ from ninja.pagination import LimitOffsetPagination, paginate from redis.client import Redis -from trustmarks.lib import add_trustmark, get_expiry, get_trustmark +from trustmarks.lib import add_trustmark, get_expiry from trustmarks.models import TrustMark, TrustMarkType api = NinjaAPI() @@ -56,7 +57,7 @@ class TrustMarkSchema(Schema): class TrustMarkOutSchema(Schema): id: int domain: str - expire_at: float + expire_at: datetime autorenew: bool | None = None valid_for: int | None = None renewal_time: int | None = None @@ -64,6 +65,11 @@ class TrustMarkOutSchema(Schema): mark: str | None = None + +class TrustMarkRenewSchema(Schema): + trustmark_id: int + + class Message(Schema): message: str id: int = 0 @@ -225,20 +231,26 @@ def create_trust_mark(request: HttpRequest, data: TrustMarkSchema): mark = add_trustmark(tm.domain, tmt.tmtype, tm.valid_for, con) # Adds the newly created JWT in the response - setattr(tm, "mark", mark) - setattr(tm, "expire_at", get_expiry(mark)) + tm.mark = mark + expiry = datetime.fromtimestamp(get_expiry(mark)) + tm.expire_at = expiry + tm.save() return 201, tm else: - mark = get_trustmark(tm.domain, tmt.tmtype, con) - if isinstance(mark, str): - setattr(tm, "mark", mark) - setattr(tm, "expire_at", get_expiry(mark)) - return 403, tm - else: - return 500, {"message": "TrustMark already exists but could not be fetched."} + return 403, tm except Exception as e: print(e) return 500, {"message": "Error while creating a new TrustMark."} +@router.get( + "/trustmarks", + response={200: list[TrustMarkOutSchema], 403: TrustMarkOutSchema, 404: Message, 500: Message}, +) +@paginate(LimitOffsetPagination) +def get_trustmark_list(request: HttpRequest): + """Returns a list of existing TrustMarks.""" + return TrustMark.objects.all() + + api.add_router("", router) From b5b1b83c712770b2653db4058c7a1a9ebd30433d Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 15 Sep 2025 14:07:32 +0200 Subject: [PATCH 54/85] Reformats via ruff --- admin/inmoradmin/api.py | 1 - admin/trustmarks/migrations/0001_initial.py | 23 +++++---------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index d8e4b1e..f0d90eb 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -65,7 +65,6 @@ class TrustMarkOutSchema(Schema): mark: str | None = None - class TrustMarkRenewSchema(Schema): trustmark_id: int diff --git a/admin/trustmarks/migrations/0001_initial.py b/admin/trustmarks/migrations/0001_initial.py index 0cf1bf8..8eb6fbd 100644 --- a/admin/trustmarks/migrations/0001_initial.py +++ b/admin/trustmarks/migrations/0001_initial.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] @@ -31,11 +30,7 @@ class Migration(migrations.Migration): ("active", models.BooleanField(default=True)), ], options={ - "indexes": [ - models.Index( - fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx" - ) - ], + "indexes": [models.Index(fields=["tmtype"], name="trustmarks__tmtype_c08ed8_idx")], }, ), migrations.CreateModel( @@ -52,9 +47,7 @@ class Migration(migrations.Migration): ), ( "added", - models.DateTimeField( - db_default=django.db.models.functions.datetime.Now() - ), + models.DateTimeField(db_default=django.db.models.functions.datetime.Now()), ), ("domain", models.CharField()), ("active", models.BooleanField()), @@ -73,16 +66,10 @@ class Migration(migrations.Migration): ], options={ "indexes": [ - models.Index( - fields=["domain"], name="trustmarks__domain_101ea9_idx" - ), - models.Index( - fields=["active"], name="trustmarks__active_292a4b_idx" - ), + models.Index(fields=["domain"], name="trustmarks__domain_101ea9_idx"), + models.Index(fields=["active"], name="trustmarks__active_292a4b_idx"), models.Index(fields=["tmt"], name="trustmarks__tmt_id_4c0dfb_idx"), - models.Index( - fields=["expire_at"], name="trustmarks__expire__f152b2_idx" - ), + models.Index(fields=["expire_at"], name="trustmarks__expire__f152b2_idx"), ], "unique_together": {("tmt", "domain")}, }, From 9d915443cd39dfb1af9c88f9e76be8433f925bf0 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 16 Sep 2025 11:42:17 +0200 Subject: [PATCH 55/85] Adds ALG for the JSON keys --- scripts/create-keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/create-keys.py b/scripts/create-keys.py index 3d9e554..f68259a 100755 --- a/scripts/create-keys.py +++ b/scripts/create-keys.py @@ -13,7 +13,7 @@ print("private.json already exists.") sys.exit(0) -key = jwk.JWK.generate(kty="RSA", size=2048, use="sig") +key = jwk.JWK.generate(kty="RSA", size=2048, use="sig", alg="RS256") key.kid = key.thumbprint() data = key.export_public() with open("public.json", "w") as f: From d6465eab21790fcdd99589d8d31fd97cd561417b Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 16 Sep 2025 13:19:29 +0200 Subject: [PATCH 56/85] Adds API for trustmark lists --- admin/tests/test_api.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index bd739d4..0717cfd 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -213,3 +213,32 @@ def test_trustmark_create_twice(db, loadredis): payload = get_payload(jwt_token) self.assertEqual(domain, payload.get("sub")) self.assertEqual("https://example.com/trust_mark_type", payload.get("trust_mark_type")) + + +@pytest.mark.django_db +def test_trustmark_list(db, loadredis): + domain0 = "https://fakerp0.labb.sunet.se" + domain1 = "https://fakerp1.labb.sunet.se" + + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + # Add the first trustmark + data = {"tmt": 2, "domain": domain0} + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 201) + # Add the second trustmark + data = {"tmt": 2, "domain": domain1} + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 201) + + response = client.get("/trustmarks") + self.assertEqual(response.status_code, 200) + + # Now verify the data we received + resp = response.json() + self.assertTrue(isinstance(resp.get("items"), list)) + self.assertEqual(2, resp["count"]) + + + From 89eaf226efd6f1bdb4fbfb8a1708bb1d86cdb0a4 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 16 Sep 2025 13:19:48 +0200 Subject: [PATCH 57/85] Adds more options to justfile --- justfile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 81ede65..d6f3a23 100644 --- a/justfile +++ b/justfile @@ -37,7 +37,7 @@ test-ta: # To run django tests for admin [working-directory: 'admin'] test-admin: - uv run pytest -vvv + uv run pytest -vvv -s # Test target for both rust and django code test: test-ta test-admin @@ -70,8 +70,8 @@ up: down: docker compose down -t-admin: - docker compose exec admin pytest -vvv +t-admin *FLAGS: + docker compose exec admin pytest -vvv {{FLAGS}} debug-ta: docker compose run --rm ta /bin/bash @@ -83,3 +83,9 @@ debug-admin: clean: rm -rf .venv rm -f public.json private.json admin/private.json + +# To recreate db/redis on Fedora +recreate-fedora: + sudo rm -rf ./db ./redis + mkdir db redis + sudo chcon -Rt container_file_t ./db ./redis From d5fd5654815927c2f78b6134428a324af73b63bb Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 16 Sep 2025 13:40:12 +0200 Subject: [PATCH 58/85] Adds trustmark renew and test --- admin/inmoradmin/api.py | 23 +++++++++++++++++++++++ admin/tests/test_api.py | 16 ++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index f0d90eb..09b63cd 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -252,4 +252,27 @@ def get_trustmark_list(request: HttpRequest): return TrustMark.objects.all() +@router.post( + "/trustmarks/{int:tmid}/renew", + response={200: TrustMarkOutSchema, 403: TrustMarkOutSchema, 404: Message, 500: Message}, +) +def renew_trustmark(request: HttpRequest, tmid: int): + """Renews a TrustMark""" + try: + tm = TrustMark.objects.get(id=tmid) + con: Redis = get_redis_connection("default") + mark = add_trustmark(tm.domain, tm.tmt.tmtype, tm.valid_for, con) + # Adds the newly created JWT in the response + tm.mark = mark + expiry = datetime.fromtimestamp(get_expiry(mark)) + tm.expire_at = expiry + tm.save() + return 200, tm + except TrustMark.DoesNotExist: + return 404, {"message": "TrustMark does not exist." ,"id": tmid} + except Exception as e: + print(e) + return 500, {"message": "Error while creating a new TrustMark."} + + api.add_router("", router) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 0717cfd..b4176fb 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -241,4 +241,20 @@ def test_trustmark_list(db, loadredis): self.assertEqual(2, resp["count"]) +@pytest.mark.django_db +def test_trustmark_renew(db, loadredis): + domain0 = "https://fakerp0.labb.sunet.se" + + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + # Add the first trustmark + data = {"tmt": 2, "domain": domain0} + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 201) + resp = response.json() + response = client.post(f"/trustmarks/{resp['id']}/renew") + self.assertEqual(response.status_code, 200) + # TODO: Now verify the rewnewd trustmark + From 230ef9387101b7f7181478c25c78ee2fb2336cf5 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 16 Sep 2025 13:40:52 +0200 Subject: [PATCH 59/85] Fixes typo in docstring --- admin/inmoradmin/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 09b63cd..2add3cf 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -181,7 +181,7 @@ def update_trust_mark_type(request: HttpRequest, tmtid: int, data: TrustMarkType response={201: TrustMarkOutSchema, 403: TrustMarkOutSchema, 404: Message, 500: Message}, ) def create_trust_mark(request: HttpRequest, data: TrustMarkSchema): - """Creates a new TrustMarak for given domain nd TrustMarkType ID.""" + """Creates a new TrustMark for a given domain and TrustMarkType ID.""" # First get the TrustMarkType try: tmt = TrustMarkType.objects.get(id=data.tmt) From 0ac6e873dbf778018b6ee25bab8e5ac01713d0d9 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 16 Sep 2025 13:43:19 +0200 Subject: [PATCH 60/85] Reformats based on ruff --- admin/inmoradmin/api.py | 2 +- admin/tests/test_api.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 2add3cf..5be0cd7 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -269,7 +269,7 @@ def renew_trustmark(request: HttpRequest, tmid: int): tm.save() return 200, tm except TrustMark.DoesNotExist: - return 404, {"message": "TrustMark does not exist." ,"id": tmid} + return 404, {"message": "TrustMark does not exist.", "id": tmid} except Exception as e: print(e) return 500, {"message": "Error while creating a new TrustMark."} diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index b4176fb..cf19d8a 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -256,5 +256,3 @@ def test_trustmark_renew(db, loadredis): response = client.post(f"/trustmarks/{resp['id']}/renew") self.assertEqual(response.status_code, 200) # TODO: Now verify the rewnewd trustmark - - From 35ac3eb9f8c0a9c0823f633addd21fe2be4b46f1 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Wed, 17 Sep 2025 08:28:28 +0200 Subject: [PATCH 61/85] Adds TrustMark update API and test --- admin/inmoradmin/api.py | 31 ++++++++++++++++++++++++++++--- admin/tests/test_api.py | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 5be0cd7..9b9d91d 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -65,8 +65,9 @@ class TrustMarkOutSchema(Schema): mark: str | None = None -class TrustMarkRenewSchema(Schema): - trustmark_id: int +class TrustMarkUpdateSchema(Schema): + autorenew: bool | None = None + active: bool | None = None class Message(Schema): @@ -254,7 +255,7 @@ def get_trustmark_list(request: HttpRequest): @router.post( "/trustmarks/{int:tmid}/renew", - response={200: TrustMarkOutSchema, 403: TrustMarkOutSchema, 404: Message, 500: Message}, + response={200: TrustMarkOutSchema, 404: Message, 500: Message}, ) def renew_trustmark(request: HttpRequest, tmid: int): """Renews a TrustMark""" @@ -275,4 +276,28 @@ def renew_trustmark(request: HttpRequest, tmid: int): return 500, {"message": "Error while creating a new TrustMark."} +@router.post( + "/trustmarks/{int:tmid}", + response={200: TrustMarkOutSchema, 404: Message, 500: Message}, +) +def update_trustmark(request: HttpRequest, tmid: int, data: TrustMarkUpdateSchema): + """Update a TrustMark""" + try: + tm = TrustMark.objects.get(id=tmid) + if data.autorenew is not None: + tm.autorenew = data.autorenew + if data.active is not None: + tm.active = data.active + if not data.active: + tm.mark = None + tm.save() + # TODO: Should we remove the trustmark from redis in case it is not active? + return 200, tm + except TrustMark.DoesNotExist: + return 404, {"message": "TrustMark does not exist.", "id": tmid} + except Exception as e: + print(e) + return 500, {"message": "Error while creating a new TrustMark."} + + api.add_router("", router) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index cf19d8a..34a87ca 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -256,3 +256,23 @@ def test_trustmark_renew(db, loadredis): response = client.post(f"/trustmarks/{resp['id']}/renew") self.assertEqual(response.status_code, 200) # TODO: Now verify the rewnewd trustmark + + +@pytest.mark.django_db +def test_trustmark_update(db, loadredis): + domain0 = "https://fakerp0.labb.sunet.se" + + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + # Add the first trustmark + data = {"tmt": 2, "domain": domain0} + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 201) + resp = response.json() + update_data = {"autorenew": False, "active": False} + response = client.post(f"/trustmarks/{resp['id']}", json=update_data) + self.assertEqual(response.status_code, 200) + resp = response.json() + self.assertEqual(False, resp.get("autorenew")) + self.assertEqual(False, resp.get("active")) From 71b3dbbdcef615fd13cbb8b86605d5e4641d29ce Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Wed, 17 Sep 2025 09:01:50 +0200 Subject: [PATCH 62/85] Lists TrustMarks per domain and tests --- admin/inmoradmin/api.py | 23 ++++++++++++++++++++--- admin/tests/test_api.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 9b9d91d..72acd71 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Any +import pytz from django.conf import settings from django.http import HttpRequest from django_redis import get_redis_connection @@ -70,6 +71,10 @@ class TrustMarkUpdateSchema(Schema): active: bool | None = None +class TrustMarkListSchema(Schema): + domain: str + + class Message(Schema): message: str id: int = 0 @@ -232,7 +237,7 @@ def create_trust_mark(request: HttpRequest, data: TrustMarkSchema): mark = add_trustmark(tm.domain, tmt.tmtype, tm.valid_for, con) # Adds the newly created JWT in the response tm.mark = mark - expiry = datetime.fromtimestamp(get_expiry(mark)) + expiry = datetime.fromtimestamp(get_expiry(mark), pytz.utc) tm.expire_at = expiry tm.save() return 201, tm @@ -243,6 +248,18 @@ def create_trust_mark(request: HttpRequest, data: TrustMarkSchema): return 500, {"message": "Error while creating a new TrustMark."} +@router.post( + "/trustmarks/list", + response={200: list[TrustMarkOutSchema], 403: TrustMarkOutSchema, 404: Message, 500: Message}, +) +@paginate(LimitOffsetPagination) +def get_trustmark_list_perdomain(request: HttpRequest, data: TrustMarkListSchema): + """Returns a list of existing TrustMarks for a given domain.""" + if data.domain: + return TrustMark.objects.filter(domain=data.domain) + return TrustMark.objects.all() + + @router.get( "/trustmarks", response={200: list[TrustMarkOutSchema], 403: TrustMarkOutSchema, 404: Message, 500: Message}, @@ -265,7 +282,7 @@ def renew_trustmark(request: HttpRequest, tmid: int): mark = add_trustmark(tm.domain, tm.tmt.tmtype, tm.valid_for, con) # Adds the newly created JWT in the response tm.mark = mark - expiry = datetime.fromtimestamp(get_expiry(mark)) + expiry = datetime.fromtimestamp(get_expiry(mark), pytz.utc) tm.expire_at = expiry tm.save() return 200, tm @@ -276,7 +293,7 @@ def renew_trustmark(request: HttpRequest, tmid: int): return 500, {"message": "Error while creating a new TrustMark."} -@router.post( +@router.put( "/trustmarks/{int:tmid}", response={200: TrustMarkOutSchema, 404: Message, 500: Message}, ) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 34a87ca..686403a 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -241,6 +241,33 @@ def test_trustmark_list(db, loadredis): self.assertEqual(2, resp["count"]) +@pytest.mark.django_db +def test_trustmark_list_entity(db, loadredis): + domain0 = "https://fakerp0.labb.sunet.se" + domain1 = "https://fakerp1.labb.sunet.se" + + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + # Add the first trustmark + data = {"tmt": 2, "domain": domain0} + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 201) + # Add the second trustmark + data = {"tmt": 2, "domain": domain1} + response = client.post("/trustmarks", json=data) + self.assertEqual(response.status_code, 201) + + data = {"domain": domain0} + response = client.post(f"/trustmarks/list", json=data) + self.assertEqual(response.status_code, 200) + + # Now verify the data we received + resp = response.json() + self.assertTrue(isinstance(resp.get("items"), list)) + self.assertEqual(1, resp["count"]) + + @pytest.mark.django_db def test_trustmark_renew(db, loadredis): domain0 = "https://fakerp0.labb.sunet.se" @@ -271,7 +298,7 @@ def test_trustmark_update(db, loadredis): self.assertEqual(response.status_code, 201) resp = response.json() update_data = {"autorenew": False, "active": False} - response = client.post(f"/trustmarks/{resp['id']}", json=update_data) + response = client.put(f"/trustmarks/{resp['id']}", json=update_data) self.assertEqual(response.status_code, 200) resp = response.json() self.assertEqual(False, resp.get("autorenew")) From 530828136fb1bc722a84064eed4e0b8f2aaef9d4 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Wed, 17 Sep 2025 14:59:52 +0200 Subject: [PATCH 63/85] Uses latest oidf_metadata_policy --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/lib.rs | 61 +++++++----------------------------------------------- 3 files changed, 11 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0e7bba..167e82d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,9 +1611,9 @@ dependencies = [ [[package]] name = "oidfed_metadata_policy" -version = "0.0.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd164bf2307ba16568a106eb73b3c979ad4186e66d9b890618ebb2dac790b41" +checksum = "9a6b3874a25a898040a2fa1a66f13ad7108aa55bb085d5487ceee7b35a1c120a" dependencies = [ "anyhow", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index e207132..ae7739f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ tokio = { version = "1", features = ["full"] } env_logger = "0.11.8" log = "0.4.27" base64 = "0.22" -oidfed_metadata_policy = "0.0.1" +oidfed_metadata_policy = "0.3.0" anyhow = "1.0.98" toml = "0.9.0" clap = { version = "4.5.41", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs index edc5980..c0c407e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1039,67 +1039,22 @@ pub async fn resolve_entity( } else { // Means the final entity statement // We should now apply the merged policy at val to the metadata claim - let mut meta_keys: HashSet = HashSet::new(); + let metadata = res.payload.claim("metadata").unwrap().as_object().unwrap(); eprintln!( "\nFinal policy checking: val= {:?}\n\n metadata= {:?}\n\n", &val, metadata ); - // If the particular key of metadata exists in policy, then only we apply the - // policy on the metata - for (mkey, mvalue) in metadata.iter() { - // Check for the key - if val.contains_key(mkey) { - // Now we need that particular policy and actual metadata for that - // part. - let mpolicy = val.get(mkey).unwrap().as_object().unwrap(); - let result = - resolve_metadata_policy(mpolicy, mvalue.as_object().unwrap()); - // Now we have the result for one particular metadata - // If it is Okay, then we should put the resolved metadata to the val - // - match result { - Ok(v) => { - let temp = v.as_object().unwrap().get(mkey).unwrap(); - val.insert(mkey.clone(), temp.clone()); - // Now keep a note that we have used this key - meta_keys.insert(mkey.clone()); - } - Err(_) => { - return error_response_400( - "invalid_trust_chain", - "received error in applying metadata policy on metadata", - ); - } - } - } else { - // Here the policy object does not have the key of the metadata, means - // we directly copy it over. - meta_keys.insert(mkey.clone()); - val.insert(mkey.clone(), mvalue.clone()); - } - } - // Now remove any extra key/value pair from the final resolved metadata. - // These extra key/values were part of policy but does not matter for this - // metadata. - let mut to_remove = Vec::new(); - for (key, _) in val.iter() { - if !meta_keys.contains(key) { - // Then remove it - to_remove.push(key.clone()); + match apply_policy_on_metadata(val, &metadata) { + Ok(val) => Some(val), + Err(_) => { + return error_response_400( + "invalid_trust_chain", + "received error in applying metadata policy on metadata", + ); } } - // Now all extra key/value - for key in to_remove.iter() { - val.remove(key); - } - // - println!("Succesfully applied metadata policy on metadata"); - println!("{:?}\n", &val); - - // No error, all good. - Some(val) } } From c972f9ef46953767330784a1f8e0e54c7f147053 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Wed, 17 Sep 2025 15:13:00 +0200 Subject: [PATCH 64/85] Fixes lint issues --- admin/tests/test_api.py | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 686403a..532eca5 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -259,7 +259,7 @@ def test_trustmark_list_entity(db, loadredis): self.assertEqual(response.status_code, 201) data = {"domain": domain0} - response = client.post(f"/trustmarks/list", json=data) + response = client.post("/trustmarks/list", json=data) self.assertEqual(response.status_code, 200) # Now verify the data we received diff --git a/src/lib.rs b/src/lib.rs index c0c407e..ab92b3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1046,7 +1046,7 @@ pub async fn resolve_entity( &val, metadata ); - match apply_policy_on_metadata(val, &metadata) { + match apply_policy_on_metadata(val, metadata) { Ok(val) => Some(val), Err(_) => { return error_response_400( From 9629329733bf86e4840864979cc47beabfcca98d Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 22 Sep 2025 20:05:51 +0200 Subject: [PATCH 65/85] Runs ta in debug mode --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index a5579f5..3c89626 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,8 @@ services: redis: condition: service_healthy command: ./inmor -c taconfig.toml + environment: + - 'RUST_LOG=debug' ports: - "8080:8080" db: @@ -77,3 +79,4 @@ services: networks: inmor-net: driver: bridge + From 4bd204cadafc7874a18dfe6c848bc57aa9980fd1 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 22 Sep 2025 20:06:28 +0200 Subject: [PATCH 66/85] Uses a branch of oidfed_metadata_policy --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 167e82d..ac5b634 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,9 +1611,8 @@ dependencies = [ [[package]] name = "oidfed_metadata_policy" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a6b3874a25a898040a2fa1a66f13ad7108aa55bb085d5487ceee7b35a1c120a" +version = "0.4.0" +source = "git+https://github.com/SUNET/oidfed_metadata_policy?branch=entity_policy#bd562686102fdce3fa81910891130ce3f6518707" dependencies = [ "anyhow", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index ae7739f..b34293b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ tokio = { version = "1", features = ["full"] } env_logger = "0.11.8" log = "0.4.27" base64 = "0.22" -oidfed_metadata_policy = "0.3.0" +oidfed_metadata_policy = { git = "https://github.com/SUNET/oidfed_metadata_policy", branch = "entity_policy"} anyhow = "1.0.98" toml = "0.9.0" clap = { version = "4.5.41", features = ["derive"] } From 4c9fe200dac27ad70d2a0c047da2c29b83499fc5 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Mon, 22 Sep 2025 20:20:38 +0200 Subject: [PATCH 67/85] Uses the new policy function --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ab92b3d..98de93b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1046,8 +1046,10 @@ pub async fn resolve_entity( &val, metadata ); - match apply_policy_on_metadata(val, metadata) { - Ok(val) => Some(val), + // Here the policy contains for every kind of entity. + + match apply_policy_document_on_metadata(val, metadata) { + Ok(applied) => Some(applied), Err(_) => { return error_response_400( "invalid_trust_chain", From 83b40373338d1bbca6514642ee475bdc59102215 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 23 Sep 2025 13:57:01 +0200 Subject: [PATCH 68/85] Adds more FIXME comments for inmor --- src/lib.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 98de93b..66100d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -858,6 +858,8 @@ pub async fn resolve_entity_to_trustanchor( let vjwt = VerifiedJWT::new(original_ec, &opayload, false, false); result.push(vjwt); } + // FIXME: Store the singing KID from the header so that we can verify it below. + // Now find the authority_hints let authority_hints = match opayload.claim("authority_hints") { Some(v) => v, @@ -912,7 +914,11 @@ pub async fn resolve_entity_to_trustanchor( Ok(value) => value, Err(_) => continue, }; - // The above function verify_jwt_with_jwks now has error handling part. + // FIXME: An Entity Statement is signed using a key from the jwks claim in the next. + // Restating this symbolically, for each j = 0,...,i-1, ES[j] is signed by + // a key in ES[j+1]["jwks"]. + // Means now we should verify that the subject's signing key is one of the key in the JWKS + // of the subordinate statment. if ta_flag { // Means this is the end of resolving let vjwt = VerifiedJWT::new(sub_statement, &subs_payload, true, false); @@ -1047,7 +1053,8 @@ pub async fn resolve_entity( ); // Here the policy contains for every kind of entity. - + // FIXME: We should have the full policy document including + // any forced metadata from the superior. match apply_policy_document_on_metadata(val, metadata) { Ok(applied) => Some(applied), Err(_) => { From ea33d88d251fd16303c4ec8b6abb43288aa88704 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Tue, 23 Sep 2025 14:03:33 +0200 Subject: [PATCH 69/85] Adds example annotation for API docs --- admin/inmoradmin/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 72acd71..cb83158 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any +from typing import Annotated, Any import pytz from django.conf import settings @@ -7,6 +7,7 @@ from django_redis import get_redis_connection from ninja import NinjaAPI, Router, Schema from ninja.pagination import LimitOffsetPagination, paginate +from pydantic import Field from redis.client import Redis from trustmarks.lib import add_trustmark, get_expiry @@ -23,7 +24,7 @@ class TrustMarkTypeGetSchema(Schema): class TrustMarkTypeSchema(Schema): - tmtype: str + tmtype: Annotated[str, Field(description="URL to describe the Trust Mark Type.")] autorenew: bool = DEFAULTS["trustmarktype"]["autorenew"] valid_for: int = DEFAULTS["trustmarktype"]["valid_for"] renewal_time: int = DEFAULTS["trustmarktype"]["renewal_time"] From a3c4592ecd6430dd9896703f8827891c8c853a6e Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 08:47:03 +0200 Subject: [PATCH 70/85] Adds oidfpolicy as python dependency --- admin/pyproject.toml | 1 + admin/requirements-dev.txt | 1 + admin/uv.lock | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/admin/pyproject.toml b/admin/pyproject.toml index dbabe53..ad1287b 100644 --- a/admin/pyproject.toml +++ b/admin/pyproject.toml @@ -8,6 +8,7 @@ requires-python = '>=3.13' dependencies = [ "jwcrypto", + "oidfpolicy", "httpx", "redis", "django", diff --git a/admin/requirements-dev.txt b/admin/requirements-dev.txt index 6f12e95..9c2a97b 100644 --- a/admin/requirements-dev.txt +++ b/admin/requirements-dev.txt @@ -24,3 +24,4 @@ humanreadable pytest-django python-dotenv pytest-dotenv +oidfpolicy diff --git a/admin/uv.lock b/admin/uv.lock index a1ffaed..218d97b 100644 --- a/admin/uv.lock +++ b/admin/uv.lock @@ -379,6 +379,7 @@ dependencies = [ { name = "httpx" }, { name = "humanreadable" }, { name = "jwcrypto" }, + { name = "oidfpolicy" }, { name = "port-for" }, { name = "psycopg2-binary" }, { name = "pydantic" }, @@ -409,6 +410,7 @@ requires-dist = [ { name = "httpx" }, { name = "humanreadable" }, { name = "jwcrypto" }, + { name = "oidfpolicy" }, { name = "port-for" }, { name = "psycopg2-binary" }, { name = "pydantic" }, @@ -471,6 +473,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "oidfpolicy" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/c9/47cb904761064fa48db1cf8858ae82419fc7e08f06634cbde320975847a3/oidfpolicy-0.1.0.tar.gz", hash = "sha256:2127bd254c2709c541f65d209c623f0bd89e2e5afba627a87f94010ae8689de4", size = 9426, upload-time = "2025-09-23T12:13:17.359Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/42/2f04872641edd174c84e2ddb7cefaba6fb846cfdd5edbc0861c677a1d559/oidfpolicy-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bf00871f63b9a067ff6b6db0d4bff8654ae05730708cfb4359e63988c046db78", size = 870990, upload-time = "2025-09-23T12:13:08.609Z" }, + { url = "https://files.pythonhosted.org/packages/99/c1/babd02d5593370930c1ea29eeba3622f2a7e57f3d864a7922fd545510073/oidfpolicy-0.1.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:50cfb556b3be350039d903f4ce727650560236feb9fd50dbe0e43dcdb08a2dea", size = 802588, upload-time = "2025-09-23T12:13:06.838Z" }, + { url = "https://files.pythonhosted.org/packages/53/47/edc14b684f4b780528d36a6fc6a6d0761257883f4b92f1135834e19e3d35/oidfpolicy-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5cc51dae4ca901077c62dc2e90503f2c70660d668ceff45d1e6dbb795e7d37f", size = 885767, upload-time = "2025-09-23T12:12:55.955Z" }, + { url = "https://files.pythonhosted.org/packages/49/b8/90ae17e7ea9ae1853fb877a759b76afed8a939977bc1ba1c47f73b8b08db/oidfpolicy-0.1.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:947a79d2f82b21dd0665c1869ebb3b867aa07399ebed65b559a902ee6193e1f2", size = 872511, upload-time = "2025-09-23T12:12:57.714Z" }, + { url = "https://files.pythonhosted.org/packages/c9/25/e008c648a7c6b1f41c6705375d02ad7c9621b7e56d7983351a26a5e227da/oidfpolicy-0.1.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ccd55f0df63798743334a722cd5ef1444379373695cb66d0e6e8d05ef938389", size = 1124865, upload-time = "2025-09-23T12:12:59.433Z" }, + { url = "https://files.pythonhosted.org/packages/70/d4/cd344b362e0a5b5c49ab6feb8c2d70a7bbda266394e15440442580435cb1/oidfpolicy-0.1.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1fdeb357affa69ab80d2bbe8f542ff6a2b2dd848dbe21ffb11c527e6da6f1c49", size = 948076, upload-time = "2025-09-23T12:13:01.371Z" }, + { url = "https://files.pythonhosted.org/packages/6a/71/29f30b652b09f5206314c23e4da0017f3d1537908ba37be9576bed9d7b90/oidfpolicy-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371749becd3d1a7c9f7e0b0e024ea0b3f389a9f1ed3d598df520b78c7f4442c9", size = 946985, upload-time = "2025-09-23T12:13:05.062Z" }, + { url = "https://files.pythonhosted.org/packages/cf/69/1d9aac1150f9b79817084e0b0194ac11bc378411a6369de6d7d0c24d4c2b/oidfpolicy-0.1.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b5f52f80a848271e81c435dbe79f76388a7925ed12355ef6cd42f8061182a1c", size = 967074, upload-time = "2025-09-23T12:13:03.113Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f9/665cd526d8903d8991565811aed973efc9a98a27e48024d66b2e25d6eb0b/oidfpolicy-0.1.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:03704e6b267c035259ee73778a5276e620861cffd50209dfc618a79668a60e2f", size = 1066984, upload-time = "2025-09-23T12:13:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/33/ff/0f5200793ee92a0487ce4b2530532e233417b920f3d0984ab25a10cc280d/oidfpolicy-0.1.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1faf74c4b768fc7b65ca3583babe941e52bc638a0b56dc1cd02e15ffa6cbf950", size = 1136363, upload-time = "2025-09-23T12:13:12.022Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d5/e7f5010e6bfa4f6d7e093ed44554bb72304ecad22eba89ada74151277b62/oidfpolicy-0.1.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:1d5bf2333a7f1e049042f71bea056b26a42a8cec5330d7ef894eff73288383f8", size = 1102334, upload-time = "2025-09-23T12:13:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/70b82acb0a8aa9924e4c487069c8591eade22b5167f4ba88c470bf359242/oidfpolicy-0.1.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8561e4a9f077b949bdfd10dd7cc378b01f994d04753b84f7595664c4252f12ba", size = 1115541, upload-time = "2025-09-23T12:13:16.171Z" }, +] + [[package]] name = "packaging" version = "25.0" From 47578b6b06a373a96bd28a7b41b4d87346cc3f55 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 08:48:08 +0200 Subject: [PATCH 71/85] Adds new entity model --- admin/entities/migrations/0001_initial.py | 10 ++++++++-- admin/entities/models.py | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/admin/entities/migrations/0001_initial.py b/admin/entities/migrations/0001_initial.py index 6ef01d6..c5d45c6 100644 --- a/admin/entities/migrations/0001_initial.py +++ b/admin/entities/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.3 on 2025-07-04 14:01 +# Generated by Django 5.2.6 on 2025-09-23 12:42 import django.db.models.functions.datetime from django.db import migrations, models @@ -27,10 +27,16 @@ class Migration(migrations.Migration): models.DateTimeField(db_default=django.db.models.functions.datetime.Now()), ), ("entityid", models.CharField(unique=True)), + ("valid_for", models.IntegerField(default=8760)), + ("autorenew", models.BooleanField(default=False)), + ("metadata_db", models.CharField()), + ("required_trustmarks", models.CharField(null=True)), ], options={ "indexes": [ - models.Index(fields=["entityid"], name="entities_su_entityi_0bfba0_idx") + models.Index(fields=["entityid"], name="entities_su_entityi_0bfba0_idx"), + models.Index(fields=["valid_for"], name="entities_su_valid_f_e25cdc_idx"), + models.Index(fields=["autorenew"], name="entities_su_autoren_f0d71e_idx"), ], }, ), diff --git a/admin/entities/models.py b/admin/entities/models.py index 9510b5f..9641358 100644 --- a/admin/entities/models.py +++ b/admin/entities/models.py @@ -7,6 +7,10 @@ class Subordinate(models.Model): id: int added = models.DateTimeField(db_default=Now()) entityid = models.CharField(unique=True) + valid_for = models.IntegerField(default=8760) # Means 365 days + autorenew = models.BooleanField(default=False) + metadata_db = models.CharField() + required_trustmarks = models.CharField(null=True) def __str__(self): return self.entityid @@ -14,4 +18,6 @@ def __str__(self): class Meta: indexes = [ models.Index(fields=["entityid"]), + models.Index(fields=["valid_for"]), + models.Index(fields=["autorenew"]), ] From 401e47a2e707f7f8d2d2f195380da94311445b84 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 08:50:24 +0200 Subject: [PATCH 72/85] Fetches and applies server policy on metadata --- admin/entities/lib.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/admin/entities/lib.py b/admin/entities/lib.py index ef0efb3..3669f1f 100644 --- a/admin/entities/lib.py +++ b/admin/entities/lib.py @@ -6,11 +6,11 @@ import httpx from django.conf import settings -from jwcrypto import jwt +from jwcrypto import jwk, jwt from jwcrypto.jwk import JWK, JWKSet from jwcrypto.jwt import JWT +from oidfpolicy import apply_policy, merge_policies from pydantic import BaseModel - from redis import Redis INSIDE_CONTAINER = os.environ.get("INSIDE_CONTAINER") @@ -22,6 +22,40 @@ class SubordinateRequest(BaseModel): entity: str +def fetch_entity_configuration( + entityid: str, official_metadata: dict[Any, Any], keys: dict[Any, Any] | None = None +) -> JWT: + """Fetches the entity configuration and returns verified JWT. + + + :args entityid: str the entity_id + + :returns: JWT representation + """ + # Let us get the JWKS + if keys is not None: + keys_str = json.dumps(keys) + keyset = jwk.JWKSet.from_json(keys_str) + elif "openid_relying_party" in official_metadata: + keys_str = json.dumps(official_metadata["openid_relying_party"]["jwks"]) + keyset = jwk.JWKSet.from_json(keys_str) + elif "openid_provider" in official_metadata: + keys_str = json.dumps(official_metadata["openid_provider"]["jwks"]) + keyset = jwk.JWKSet.from_json(keys_str) + else: + raise ValueError("Missing JWKS") + resp = httpx.get(f"{entityid}/.well-known/openid-federation") + text = resp.text + jwt_net: JWT = jwt.JWT(jwt=text, key=keyset) + return jwt_net + + +def apply_server_policy(metadata: str): + "Verifies that we can apply our policy on the metadata." + m = apply_policy(json.dumps(settings.POLICY_DOCUMENT), metadata) + return m + + def add_subordinate(entity_id: str, r: Redis) -> str: """Adds a new subordinate to the federation. From 71d678938d5a252e24201eedd152f001eb9063ea Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 08:50:43 +0200 Subject: [PATCH 73/85] Adds example policy --- admin/inmoradmin/settings.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/admin/inmoradmin/settings.py b/admin/inmoradmin/settings.py index a90079f..e90b4dc 100644 --- a/admin/inmoradmin/settings.py +++ b/admin/inmoradmin/settings.py @@ -146,12 +146,17 @@ } } +# TA/IA configuration + SIGNING_PRIVATE_KEY = jwk.JWK.from_json(open("./private.json").read()) SIGNING_PUBLIC_KEY = jwk.JWK.from_json(open("./public.json").read()) TA_DOMAIN = "http://localhost:8080" TRUSTMARK_PROVIDER = "http://localhost:8080" # We must have this, empty dictionary is okay -METADATA_POLICY = {} +POLICY_DOCUMENT = { + "metadata_policy": {}, + "metadata": {}, +} # The following are the default values the system will use while creating new entries via API. TA_DEFAULTS = { From 2564f56e538a2d937f05a9d4e0bb72a7b563b5c8 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 08:51:01 +0200 Subject: [PATCH 74/85] Adds initial entity API This is not working yet, only parts written. --- admin/inmoradmin/api.py | 50 +++++++++++++++++++++++++++++++++++++++++ admin/tests/test_api.py | 21 +++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index cb83158..105661b 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,3 +1,4 @@ +import json from datetime import datetime from typing import Annotated, Any @@ -10,6 +11,7 @@ from pydantic import Field from redis.client import Redis +from entities.lib import apply_server_policy, fetch_entity_configuration from trustmarks.lib import add_trustmark, get_expiry from trustmarks.models import TrustMark, TrustMarkType @@ -76,6 +78,25 @@ class TrustMarkListSchema(Schema): domain: str +class EntityTypeSchema(Schema): + entityid: str + metadata: dict[Any, Any] + keys: str | None = None + required_trustmarks: str | None = None + valid_for: int | None = None + autorenew: bool | None = None + + +class EntityOutSchema(Schema): + id: int = 0 + entityid: str + metadata: dict[Any, Any] + keys: str | None = None + required_trustmarks: str | None = None + valid_for: int | None = None + autorenew: bool | None = None + + class Message(Schema): message: str id: int = 0 @@ -318,4 +339,33 @@ def update_trustmark(request: HttpRequest, tmid: int, data: TrustMarkUpdateSchem return 500, {"message": "Error while creating a new TrustMark."} +# Subordinate API + + +@router.post( + "/subordinates", + response={201: EntityOutSchema, 403: EntityOutSchema, 400: Message, 500: Message}, +) +def create_subordinate(request: HttpRequest, data: EntityTypeSchema): + "Adds a new subordinate." + # First get verified JWT from entity configuration with the keys we provided + official_metadata = data.metadata + keys: dict[Any, Any] | None = None + if data.keys: + keys = json.loads(data.keys) + + print(type(official_metadata)) + entity_jwt = fetch_entity_configuration(data.entityid, official_metadata, keys) + claims: dict[str, Any] = json.loads(entity_jwt.claims) + metadata: dict[str, Any] = claims["metadata"] + try: + _ = apply_server_policy(json.dumps(metadata)) + + except Exception as e: + print(e) + return 400, {"message": f"Could not succesfully apply POLICY on the metadata. {e}"} + + return 201, data + + api.add_router("", router) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 532eca5..ea2383a 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -1,3 +1,7 @@ +import json +import os +from typing import Any + import pytest from django.test import TestCase from jwcrypto import jwt @@ -6,6 +10,8 @@ from inmoradmin.api import router +data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") + def get_payload(token_str: str): "Helper method to get payload" @@ -303,3 +309,18 @@ def test_trustmark_update(db, loadredis): resp = response.json() self.assertEqual(False, resp.get("autorenew")) self.assertEqual(False, resp.get("active")) + + +@pytest.mark.django_db +def test_add_subordinate(db, loadredis, conf_settings): # type: ignore + "Tests adding subordinate" + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + + with open(os.path.join(data_dir, "fakerp0_metadata.json")) as fobj: + metadata = json.load(fobj) + data = {"entityid": "https://fakerp0.labb.sunet.se", "metadata": metadata} + + response = client.post("/subordinates", json=data) + self.assertEqual(response.status_code, 201) From 326e6e3fa727c93a66831601543f441410d31c69 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 17:44:24 +0200 Subject: [PATCH 75/85] Part of the code Entity creation works but need refactoring. --- admin/inmoradmin/api.py | 95 +++++++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 105661b..9e4699b 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,6 +1,6 @@ import json -from datetime import datetime -from typing import Annotated, Any +from datetime import datetime, timedelta +from typing import Annotated, Any, ClassVar import pytz from django.conf import settings @@ -8,10 +8,13 @@ from django_redis import get_redis_connection from ninja import NinjaAPI, Router, Schema from ninja.pagination import LimitOffsetPagination, paginate -from pydantic import Field +from pydantic import AnyUrl, BaseModel, BeforeValidator, ConfigDict, Field from redis.client import Redis -from entities.lib import apply_server_policy, fetch_entity_configuration +from entities.lib import (apply_server_policy, create_subordinate_statement, + fetch_entity_configuration, + update_redis_with_subordinate) +from entities.models import Subordinate from trustmarks.lib import add_trustmark, get_expiry from trustmarks.models import TrustMark, TrustMarkType @@ -78,23 +81,48 @@ class TrustMarkListSchema(Schema): domain: str +class JWKSType(BaseModel): + keys: Annotated[list[dict[str, Any]], Field(min_length=1)] + + +# We need to deserialize from DB +# class MyJWKSType(BaseModel): + # keys: Annotated[ + # list[dict[str, Any]], + # BeforeValidator(lambda v: json.loads(v)), + # Field(min_length=1), + # ] + +def process_keys_fromdb(v: str | None) -> dict[str, Any]: + "For the InternalJWKS below" + if v: + return json.loads(v) + return {} + +MyDict = Annotated[dict[str, Any], BeforeValidator(lambda v: json.loads(v))] +InternalJWKS = Annotated[dict[str, Any], BeforeValidator(lambda v: process_keys_fromdb(v))] + class EntityTypeSchema(Schema): entityid: str metadata: dict[Any, Any] - keys: str | None = None + keys: dict[Any, Any] | None = None required_trustmarks: str | None = None valid_for: int | None = None - autorenew: bool | None = None + autorenew: bool | None = True + active: bool | None = True + + class EntityOutSchema(Schema): id: int = 0 entityid: str - metadata: dict[Any, Any] - keys: str | None = None + metadata: MyDict + keys: InternalJWKS | None = None required_trustmarks: str | None = None valid_for: int | None = None autorenew: bool | None = None + active: bool | None = None class Message(Schema): @@ -352,10 +380,11 @@ def create_subordinate(request: HttpRequest, data: EntityTypeSchema): official_metadata = data.metadata keys: dict[Any, Any] | None = None if data.keys: - keys = json.loads(data.keys) + keys = data.keys - print(type(official_metadata)) - entity_jwt = fetch_entity_configuration(data.entityid, official_metadata, keys) + entity_jwt, keyset, entity_jwt_str = fetch_entity_configuration( + data.entityid, official_metadata, keys + ) claims: dict[str, Any] = json.loads(entity_jwt.claims) metadata: dict[str, Any] = claims["metadata"] try: @@ -364,8 +393,48 @@ def create_subordinate(request: HttpRequest, data: EntityTypeSchema): except Exception as e: print(e) return 400, {"message": f"Could not succesfully apply POLICY on the metadata. {e}"} - - return 201, data + if data.valid_for: + if data.valid_for > settings.SUBORDINATE_DEFAULT_VALID_FOR: # Oops, we can not allow that. + return 400, { + "message": "valid_for is greater than allowed by system default.", + "id": 0, + } + expiry = data.valid_for + else: + expiry = settings.SUBORDINATE_DEFAULT_VALID_FOR + + # Now we can build the sub ordinate statement first. + now = datetime.now() + exp = now + timedelta(hours=expiry) + # Next, we create the signed statement + signed_statement = create_subordinate_statement(data.entityid, keyset, now, exp) + # Next save the data in the database. + if keys: + keys_for_db = json.dumps(keys) + else: + keys_for_db = None + try: + sub_statement, created = Subordinate.objects.get_or_create( + entityid=data.entityid, + autorenew=data.autorenew, + metadata=json.dumps(data.metadata), + keys=keys_for_db, + valid_for=expiry, + active=data.active, + statement=signed_statement + ) + except Exception as e: + print(e) + return 500, {"message": "Error while creating a new TrustMarkType"} + # All good so far, we will update all related redis entries now. + con: Redis = get_redis_connection("default") + update_redis_with_subordinate( + data.entityid, entity_jwt_str, official_metadata, signed_statement, con + ) + if created: + return 201, sub_statement + else: + return 403, sub_statement api.add_router("", router) From 4dacdccc7e87c3c9c32998ca3cb0c7c5db9c0f00 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:30:25 +0200 Subject: [PATCH 76/85] Reformats via ruff --- admin/entities/migrations/0001_initial.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/admin/entities/migrations/0001_initial.py b/admin/entities/migrations/0001_initial.py index c5d45c6..e750477 100644 --- a/admin/entities/migrations/0001_initial.py +++ b/admin/entities/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.6 on 2025-09-23 12:42 +# Generated by Django 5.2.6 on 2025-09-25 15:47 import django.db.models.functions.datetime from django.db import migrations, models @@ -29,8 +29,11 @@ class Migration(migrations.Migration): ("entityid", models.CharField(unique=True)), ("valid_for", models.IntegerField(default=8760)), ("autorenew", models.BooleanField(default=False)), - ("metadata_db", models.CharField()), + ("metadata", models.CharField()), + ("jwks", models.CharField(null=True)), ("required_trustmarks", models.CharField(null=True)), + ("active", models.BooleanField(default=True)), + ("statement", models.CharField(null=True)), ], options={ "indexes": [ From 8a444362f6089d1d2a7886e48171eb3fdd70417b Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:30:48 +0200 Subject: [PATCH 77/85] Disables the command for future --- admin/entities/management/commands/readd_subordinates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/entities/management/commands/readd_subordinates.py b/admin/entities/management/commands/readd_subordinates.py index 75d31e0..4008b51 100644 --- a/admin/entities/management/commands/readd_subordinates.py +++ b/admin/entities/management/commands/readd_subordinates.py @@ -1,6 +1,6 @@ import djclick as click from django_redis import get_redis_connection -from entities.lib import add_subordinate + from entities.models import Subordinate @@ -13,5 +13,5 @@ def command(): subs = Subordinate.objects.all() for sub in subs: # Means we can reissue this one - add_subordinate(sub.entityid, con) + # TODO: reissue and save here click.secho(f"Reissued {sub.entityid}") From c2193658417d1f874a794514f7ac169113d7e34f Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:31:04 +0200 Subject: [PATCH 78/85] Adds system's default valid_for for subordinate --- admin/inmoradmin/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/admin/inmoradmin/settings.py b/admin/inmoradmin/settings.py index e90b4dc..9942d3b 100644 --- a/admin/inmoradmin/settings.py +++ b/admin/inmoradmin/settings.py @@ -158,6 +158,8 @@ "metadata": {}, } +SUBORDINATE_DEFAULT_VALID_FOR: int = 8760 # a year in hours + # The following are the default values the system will use while creating new entries via API. TA_DEFAULTS = { "trustmarktype": { From 6b9346839a34da0aee17ead7c79694c72893f1a2 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:31:35 +0200 Subject: [PATCH 79/85] Adds a future FIXME --- admin/trustmarks/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/admin/trustmarks/lib.py b/admin/trustmarks/lib.py index 12d09fa..eec2849 100644 --- a/admin/trustmarks/lib.py +++ b/admin/trustmarks/lib.py @@ -32,6 +32,7 @@ def add_trustmark(entity: str, trustmarktype: str, expiry: int, r: redis.Redis) sub_data = {"iss": settings.TRUSTMARK_PROVIDER} sub_data["sub"] = entity now = datetime.now() + # FIXME: timedelta should be in hours exp = now + timedelta(days=expiry) sub_data["iat"] = now.timestamp() sub_data["exp"] = exp.timestamp() From 70a9858c25f0678dbeff45045cf68583fe4ff08d Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:31:49 +0200 Subject: [PATCH 80/85] Removes old views code --- admin/entities/urls.py | 10 +------ admin/entities/views.py | 60 ----------------------------------------- 2 files changed, 1 insertion(+), 69 deletions(-) diff --git a/admin/entities/urls.py b/admin/entities/urls.py index fd9dae8..a4dd425 100644 --- a/admin/entities/urls.py +++ b/admin/entities/urls.py @@ -1,11 +1,3 @@ -from django.urls import path - -from . import views - app_name = "entities" -urlpatterns = [ - path("", views.index, name="index"), - path("add/", views.add_subordinate_entity, name="add"), - path("list/", views.list_subordinates, name="list"), -] +urlpatterns = [] diff --git a/admin/entities/views.py b/admin/entities/views.py index 56f551d..e69de29 100644 --- a/admin/entities/views.py +++ b/admin/entities/views.py @@ -1,60 +0,0 @@ -from django.core.paginator import Paginator -from django.db import IntegrityError -from django.http import HttpRequest, HttpResponse -from django.shortcuts import render -from django_redis import get_redis_connection - -from .forms import EntityForm -from .lib import SubordinateRequest, add_subordinate -from .models import Subordinate - -# Create your views here. - - -def index(request: HttpRequest) -> HttpResponse: - return render(request, "entities/index.html") - - -def list_subordinates(request: HttpRequest) -> HttpResponse: - subordinate_list = Subordinate.objects.all() - paginator = Paginator(subordinate_list, 3) - - page_number = request.GET.get("page") - page_obj = paginator.get_page(page_number) - return render(request, "entities/list.html", {"page_obj": page_obj}) - - -def add_subordinate_entity(request: HttpRequest) -> HttpResponse: - msg = "" - - if request.method == "POST": - form = EntityForm(request.POST) - # check whether it's valid: - if form.is_valid(): - subr = SubordinateRequest(entity=form.data["entity"]) - try: - e = Subordinate(entityid=subr.entity) - # save to database - e.save() - con = get_redis_connection("default") - # Next create the actual trustmark - add_subordinate(subr.entity, con) - msg = f"Added {subr.entity} as subordinated" - except IntegrityError: - msg = f"{subr.entity} was already added as subordinate." - except Exception as e: - msg = f"Failed to add {subr.entity} due to {e}" - else: - print("Falied to validate the form.") - msg = "Failed to validate the form." - else: - form = EntityForm(request.POST) - - return render( - request, - "entities/add.html", - {"form": form, "msg": msg}, - ) - - -# Create your views here. From 4eba557a41f45d4c1cf6a41920e236d43b449056 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:32:08 +0200 Subject: [PATCH 81/85] Updates on entity model --- admin/entities/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin/entities/models.py b/admin/entities/models.py index 9641358..5d8748c 100644 --- a/admin/entities/models.py +++ b/admin/entities/models.py @@ -9,8 +9,11 @@ class Subordinate(models.Model): entityid = models.CharField(unique=True) valid_for = models.IntegerField(default=8760) # Means 365 days autorenew = models.BooleanField(default=False) - metadata_db = models.CharField() + metadata = models.CharField() + jwks = models.CharField(null=True) required_trustmarks = models.CharField(null=True) + active = models.BooleanField(default=True) + statement = models.CharField(null=True) def __str__(self): return self.entityid From 652539389f3109ee38ebe353bb4e721592f7a559 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:32:28 +0200 Subject: [PATCH 82/85] Adds subordinate adding API --- admin/entities/lib.py | 99 ++++++++++++++++++++++++----------------- admin/inmoradmin/api.py | 58 +++++++++++++++--------- admin/tests/test_api.py | 29 +++++++++++- 3 files changed, 123 insertions(+), 63 deletions(-) diff --git a/admin/entities/lib.py b/admin/entities/lib.py index 3669f1f..c4cc636 100644 --- a/admin/entities/lib.py +++ b/admin/entities/lib.py @@ -24,13 +24,13 @@ class SubordinateRequest(BaseModel): def fetch_entity_configuration( entityid: str, official_metadata: dict[Any, Any], keys: dict[Any, Any] | None = None -) -> JWT: +) -> tuple[JWT, JWKSet, str]: """Fetches the entity configuration and returns verified JWT. :args entityid: str the entity_id - :returns: JWT representation + :returns: Tuple of JWT representation and JWKSet, and jwt in str format """ # Let us get the JWKS if keys is not None: @@ -47,7 +47,17 @@ def fetch_entity_configuration( resp = httpx.get(f"{entityid}/.well-known/openid-federation") text = resp.text jwt_net: JWT = jwt.JWT(jwt=text, key=keyset) - return jwt_net + return jwt_net, keyset, text + + +def merge_our_policy_ontop_subpolicy(subpolicy: dict[Any, Any]) -> str | None: + "To verify that we can succesfully merge policies." + if settings.POLICY_DOCUMENT.get("metadata_policy", {}): + merged_policy = merge_policies( + json.dumps(settings.POLICY_DOCUMENT.get("metadata_policy", {})), json.dumps(subpolicy) + ) + return merged_policy + return None def apply_server_policy(metadata: str): @@ -56,40 +66,66 @@ def apply_server_policy(metadata: str): return m -def add_subordinate(entity_id: str, r: Redis) -> str: +def create_subordinate_statement( + entityid: str, keyset: JWKSet, now: datetime, exp: datetime +) -> str: + """Creates a signed Subordinate Statement""" + # This is the data we care for now + sub_data = {"iss": settings.TA_DOMAIN} + sub_data["sub"] = entityid + # Add any server metadata if available + if settings.POLICY_DOCUMENT.get("metadata", {}): + sub_data["metadata"] = settings.POLICY_DOCUMENT.get("metadata") + # This is the metadata policy of TA defined in the settings.py + if settings.POLICY_DOCUMENT.get("metadata_policy", {}): + sub_data["metadata_policy"] = settings.POLICY_DOCUMENT.get("metadata_policy") + # creation time + sub_data["iat"] = now.timestamp() + # Expiry time of the subordinate statement + sub_data["exp"] = exp.timestamp() + # The is the subordinate's keyset + sub_data["jwks"] = keyset.export(private_keys=False) + + key = settings.SIGNING_PRIVATE_KEY + + # TODO: fix the alg value for other types of keys of TA/I + token = jwt.JWT( + header={"alg": "RS256", "kid": key.kid, "typ": "entity-statement+jwt"}, claims=sub_data + ) + token.make_signed_token(key) + token_data = token.serialize() + return token_data + + +def update_redis_with_subordinate( + entity_id: str, jwt_text: str, sub_metadata: dict[str, Any], signed_statement: str, r: Redis +) -> None: """Adds a new subordinate to the federation. This creates a subordinate statement by the TA as iss and adds the metadata of the entity (subordinate). https://openid.net/specs/openid-federation-1_0.html#name-fetch-subordinate-statement- :args entity_id: The entity_id to be added + :args keyset: The keyset of the subordinate + :args jwt_text: The str version of the JWT of the subordinate. + :args sub_metadata: The subordinate's metadata + :args singed_statement: The signed subordinate statement. :args r: Redis class from Django - :returns: String version of the signed JWT """ - resp = httpx.get(f"{entity_id}/.well-known/openid-federation") - text = resp.text - jwt_net: JWT = jwt.JWT.from_jose_token(text) - # FIXME: In future we will need the proper key to verify the signature and use only - # validated contain. - payload = json.loads(jwt_net.token.objects.get("payload").decode("utf-8")) - # TODO: Verify that the authority_hints matches with the inmor's entity_id. - # This is the data we care for now - sub_data = {"iss": settings.TA_DOMAIN} # We don't need this for subordinate statement # sub_data["authority_hints"] = payload.get("authority_hints") - metadata = payload.get("metadata") - if metadata: + if sub_metadata: # We should mark what kind of entity it is, for the /list endpoint # The treewalking code will visit the entity later, but this is to make sure # that we have some information storied at the first check. - if "openid_relying_party" in metadata: + if "openid_relying_party" in sub_metadata: # Mweans RP _ = r.sadd("inmor:rp", entity_id) logger.info(f"{entity_id} added as RP to memory database.") - elif "openid_provider" in metadata: + elif "openid_provider" in sub_metadata: # Means OP _ = r.sadd("inmor:op", entity_id) logger.info(f"{entity_id} added as OP to memory database.") @@ -97,37 +133,16 @@ def add_subordinate(entity_id: str, r: Redis) -> str: # Means we have a TA/IA _ = r.sadd("inmor:taia", entity_id) - sub_data["metadata"] = metadata - - # This is the metadata policy of TA defined in the settings.py - sub_data["metadata_policy "] = settings.METADATA_POLICY - - # We don't need trustmarks for the subordinate statements. + # We don't need trustmarks for the subordinate statements. # trust_marks = payload.get("trust_marks") # if trust_marks: # sub_data["trust_marks"] = trust_marks - # Default we keep the same expiry of the subordinate entity configuration - sub_data["exp"] = payload.get("exp") - sub_data["sub"] = payload.get("sub") - # creation time - sub_data["iat"] = datetime.now().timestamp() - sub_data["jwks"] = [settings.SIGNING_PUBLIC_KEY] - - key = settings.SIGNING_PRIVATE_KEY - - # TODO: fix the alg value for other types of keys of TA/I - token = jwt.JWT( - header={"alg": "RS256", "kid": key.kid, "typ": "entity-statement+jwt"}, claims=sub_data - ) - token.make_signed_token(key) - token_data = token.serialize() # Now we should set it in the redis - _ = r.hset("inmor:subordinates", sub_data["sub"], token_data) - _ = r.hset("inmor:subordinates:jwt", sub_data["sub"], text) + _ = r.hset("inmor:subordinates", entity_id, signed_statement) + _ = r.hset("inmor:subordinates:jwt", entity_id, jwt_text) # Add the entity in the queue for walking the tree (if any) _ = r.lpush("inmor:newsubordinate", entity_id) - return token_data def self_validate(token: jwt.JWT) -> dict[str, Any]: diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 9e4699b..66921ee 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -1,6 +1,6 @@ import json from datetime import datetime, timedelta -from typing import Annotated, Any, ClassVar +from typing import Annotated, Any import pytz from django.conf import settings @@ -8,12 +8,16 @@ from django_redis import get_redis_connection from ninja import NinjaAPI, Router, Schema from ninja.pagination import LimitOffsetPagination, paginate -from pydantic import AnyUrl, BaseModel, BeforeValidator, ConfigDict, Field +from pydantic import BaseModel, BeforeValidator, Field from redis.client import Redis -from entities.lib import (apply_server_policy, create_subordinate_statement, - fetch_entity_configuration, - update_redis_with_subordinate) +from entities.lib import ( + apply_server_policy, + create_subordinate_statement, + fetch_entity_configuration, + merge_our_policy_ontop_subpolicy, + update_redis_with_subordinate, +) from entities.models import Subordinate from trustmarks.lib import add_trustmark, get_expiry from trustmarks.models import TrustMark, TrustMarkType @@ -87,11 +91,12 @@ class JWKSType(BaseModel): # We need to deserialize from DB # class MyJWKSType(BaseModel): - # keys: Annotated[ - # list[dict[str, Any]], - # BeforeValidator(lambda v: json.loads(v)), - # Field(min_length=1), - # ] +# keys: Annotated[ +# list[dict[str, Any]], +# BeforeValidator(lambda v: json.loads(v)), +# Field(min_length=1), +# ] + def process_keys_fromdb(v: str | None) -> dict[str, Any]: "For the InternalJWKS below" @@ -99,26 +104,26 @@ def process_keys_fromdb(v: str | None) -> dict[str, Any]: return json.loads(v) return {} + MyDict = Annotated[dict[str, Any], BeforeValidator(lambda v: json.loads(v))] InternalJWKS = Annotated[dict[str, Any], BeforeValidator(lambda v: process_keys_fromdb(v))] + class EntityTypeSchema(Schema): entityid: str metadata: dict[Any, Any] - keys: dict[Any, Any] | None = None + jwks: dict[Any, Any] | None = None required_trustmarks: str | None = None valid_for: int | None = None autorenew: bool | None = True active: bool | None = True - - class EntityOutSchema(Schema): id: int = 0 entityid: str metadata: MyDict - keys: InternalJWKS | None = None + jwks: InternalJWKS | None = None required_trustmarks: str | None = None valid_for: int | None = None autorenew: bool | None = None @@ -379,13 +384,26 @@ def create_subordinate(request: HttpRequest, data: EntityTypeSchema): # First get verified JWT from entity configuration with the keys we provided official_metadata = data.metadata keys: dict[Any, Any] | None = None - if data.keys: - keys = data.keys + if data.jwks: + keys = data.jwks + # This entity_jwt is verified with the key (signature verification) entity_jwt, keyset, entity_jwt_str = fetch_entity_configuration( data.entityid, official_metadata, keys ) claims: dict[str, Any] = json.loads(entity_jwt.claims) + # TODO: If the entity has policy, then we should try to merge to veirfy. + if "metadata_policy" in claims: + sub_policy = claims.get("metadata_policy", {}) + try: + _resp = merge_our_policy_ontop_subpolicy(sub_policy) + + except Exception as e: + print(e) + return 400, { + "message": f"Could not succesfully merge TA/IA POLICY on the policy of the subordinate. {e}" + } + metadata: dict[str, Any] = claims["metadata"] try: _ = apply_server_policy(json.dumps(metadata)) @@ -396,14 +414,14 @@ def create_subordinate(request: HttpRequest, data: EntityTypeSchema): if data.valid_for: if data.valid_for > settings.SUBORDINATE_DEFAULT_VALID_FOR: # Oops, we can not allow that. return 400, { - "message": "valid_for is greater than allowed by system default.", + "message": f"valid_for is greater than allowed by system default {settings.SUBORDINATE_DEFAULT_VALID_FOR}.", "id": 0, } expiry = data.valid_for else: expiry = settings.SUBORDINATE_DEFAULT_VALID_FOR - # Now we can build the sub ordinate statement first. + # Now we can build the sub ordinate statement. now = datetime.now() exp = now + timedelta(hours=expiry) # Next, we create the signed statement @@ -418,10 +436,10 @@ def create_subordinate(request: HttpRequest, data: EntityTypeSchema): entityid=data.entityid, autorenew=data.autorenew, metadata=json.dumps(data.metadata), - keys=keys_for_db, + jwks=keys_for_db, valid_for=expiry, active=data.active, - statement=signed_statement + statement=signed_statement, ) except Exception as e: print(e) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index ea2383a..ec9bd65 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -1,6 +1,5 @@ import json import os -from typing import Any import pytest from django.test import TestCase @@ -10,6 +9,9 @@ from inmoradmin.api import router +# from pprint import pprint + + data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") @@ -324,3 +326,28 @@ def test_add_subordinate(db, loadredis, conf_settings): # type: ignore response = client.post("/subordinates", json=data) self.assertEqual(response.status_code, 201) + d1 = response.json() + self.assertEqual(True, d1.get("active")) + # This is because the keys are inside the metadata of the client + self.assertIsNone(d1.get("jwks")) + + +@pytest.mark.django_db +def test_add_subordinate_with_key(db, loadredis, conf_settings): # type: ignore + "Tests adding subordinate" + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + + with open(os.path.join(data_dir, "fakerp0_metadata_without_key.json")) as fobj: + metadata = json.load(fobj) + + with open(os.path.join(data_dir, "fakerp0_key.json")) as fobj: + keys = json.load(fobj) + data = {"entityid": "https://fakerp0.labb.sunet.se", "metadata": metadata, "jwks": keys} + + response = client.post("/subordinates", json=data) + self.assertEqual(response.status_code, 201) + d1 = response.json() + # This is because the keys are sent separately + self.assertEqual(keys, d1.get("jwks")) From dafb5dcde659db63856d0d54dbc3c6a9fb84a301 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:32:59 +0200 Subject: [PATCH 83/85] Adds more tests for entity.lib --- admin/tests/test_entitylib.py | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 admin/tests/test_entitylib.py diff --git a/admin/tests/test_entitylib.py b/admin/tests/test_entitylib.py new file mode 100644 index 0000000..b382c0f --- /dev/null +++ b/admin/tests/test_entitylib.py @@ -0,0 +1,37 @@ +import json +import os +from typing import Any + +from django.test import TestCase +from jwcrypto import jwt +from jwcrypto.common import json_decode + +from entities import lib + +data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") + + +def get_payload(token_str: str): + "Helper method to get payload" + jose = jwt.JWT.from_jose_token(token_str) + return json_decode(jose.token.objects.get("payload", "")) + + +def test_fetch_entity_configuration(): + "Tests fetching entity configuration and verification." + self = TestCase() + self.maxDiff = None + with open(os.path.join(data_dir, "fakerp0_metadata.json")) as fobj: + metadata = json.load(fobj) + _ = lib.fetch_entity_configuration("https://fakerp0.labb.sunet.se", metadata) + + +def test_fetch_entity_configuration_with_keys(): + "Tests fetching entity configuration and verification." + self = TestCase() + self.maxDiff = None + with open(os.path.join(data_dir, "fakerp0_metadata.json")) as fobj: + metadata: dict[Any, Any] = json.load(fobj) + keys = metadata["openid_relying_party"]["jwks"] + metadata.pop("openid_relying_party") + _ = lib.fetch_entity_configuration("https://fakerp0.labb.sunet.se", metadata, keys) From 42c177c492d6055d1c7f4fb5d94757a9f2987343 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:39:23 +0200 Subject: [PATCH 84/85] Adds listing subordinates API --- admin/inmoradmin/api.py | 19 ++++++++++++------- admin/tests/test_api.py | 27 ++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index 66921ee..f9e96cd 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -11,13 +11,10 @@ from pydantic import BaseModel, BeforeValidator, Field from redis.client import Redis -from entities.lib import ( - apply_server_policy, - create_subordinate_statement, - fetch_entity_configuration, - merge_our_policy_ontop_subpolicy, - update_redis_with_subordinate, -) +from entities.lib import (apply_server_policy, create_subordinate_statement, + fetch_entity_configuration, + merge_our_policy_ontop_subpolicy, + update_redis_with_subordinate) from entities.models import Subordinate from trustmarks.lib import add_trustmark, get_expiry from trustmarks.models import TrustMark, TrustMarkType @@ -454,5 +451,13 @@ def create_subordinate(request: HttpRequest, data: EntityTypeSchema): else: return 403, sub_statement +@router.get("/subordinates", response=list[EntityOutSchema]) +@paginate(LimitOffsetPagination) +def list_trust_subordinates( + request: HttpRequest, +): + """Lists all existing TrustMarkType(s) from database.""" + return Subordinate.objects.all() + api.add_router("", router) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index ec9bd65..19e87da 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -333,7 +333,7 @@ def test_add_subordinate(db, loadredis, conf_settings): # type: ignore @pytest.mark.django_db -def test_add_subordinate_with_key(db, loadredis, conf_settings): # type: ignore +def test_add_subordinate_with_key(db, loadredis): # type: ignore "Tests adding subordinate" self = TestCase() self.maxDiff = None @@ -351,3 +351,28 @@ def test_add_subordinate_with_key(db, loadredis, conf_settings): # type: ignore d1 = response.json() # This is because the keys are sent separately self.assertEqual(keys, d1.get("jwks")) + +@pytest.mark.django_db +def test_list_subordinates(db, loadredis): # type: ignore + "Tests listing subordinates" + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + + with open(os.path.join(data_dir, "fakerp0_metadata_without_key.json")) as fobj: + metadata = json.load(fobj) + + with open(os.path.join(data_dir, "fakerp0_key.json")) as fobj: + keys = json.load(fobj) + data = {"entityid": "https://fakerp0.labb.sunet.se", "metadata": metadata, "jwks": keys} + + response = client.post("/subordinates", json=data) + self.assertEqual(response.status_code, 201) + + response = client.get("/subordinates") + self.assertEqual(response.status_code, 200) + + marks = response.json() + self.assertEqual(marks["count"], 1) + + From 14ede7caf5785983d70905f53ed40461bc7455c2 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 25 Sep 2025 20:45:30 +0200 Subject: [PATCH 85/85] Gets subordinate by id --- admin/inmoradmin/api.py | 15 +++++++++++++++ admin/tests/test_api.py | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/admin/inmoradmin/api.py b/admin/inmoradmin/api.py index f9e96cd..947e61c 100644 --- a/admin/inmoradmin/api.py +++ b/admin/inmoradmin/api.py @@ -459,5 +459,20 @@ def list_trust_subordinates( """Lists all existing TrustMarkType(s) from database.""" return Subordinate.objects.all() +@router.get( + "/subordinates/{int:subid}", + response={200: EntityOutSchema, 404: Message, 500: Message}, +) +def get_trustmarktype_byid(request: HttpRequest, subid: int): + """Gets a TrustMarkType""" + try: + tmt = Subordinate.objects.get(id=subid) + return tmt + except Subordinate.DoesNotExist: + return 404, {"message": "Subordinate could not be found.", "id": subid} + except Exception as e: + print(e) + return 500, {"message": "Failed to get TrustMarkType.", "id": subid} + api.add_router("", router) diff --git a/admin/tests/test_api.py b/admin/tests/test_api.py index 19e87da..194c8d6 100644 --- a/admin/tests/test_api.py +++ b/admin/tests/test_api.py @@ -375,4 +375,28 @@ def test_list_subordinates(db, loadredis): # type: ignore marks = response.json() self.assertEqual(marks["count"], 1) +@pytest.mark.django_db +def test_get_subordinate_byid(db, loadredis): # type: ignore + "Tests listing subordinates" + self = TestCase() + self.maxDiff = None + client: TestClient = TestClient(router) + + with open(os.path.join(data_dir, "fakerp0_metadata_without_key.json")) as fobj: + metadata = json.load(fobj) + + with open(os.path.join(data_dir, "fakerp0_key.json")) as fobj: + keys = json.load(fobj) + data = {"entityid": "https://fakerp0.labb.sunet.se", "metadata": metadata, "jwks": keys} + + response = client.post("/subordinates", json=data) + self.assertEqual(response.status_code, 201) + original = response.json() + + response = client.get(f"/subordinates/{original['id']}") + self.assertEqual(response.status_code, 200) + + + new = response.json() + self.assertEqual(original, new)