From d8abe17618fae886388dcee095cc1b863b5bbdcb Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 11:00:00 +0100 Subject: [PATCH 01/12] docs: admin module docstrings Issue: #13 --- src/admin/config.py | 4 ---- src/admin/constants.py | 6 +----- src/admin/dependencies.py | 10 +--------- src/admin/exceptions.py | 6 +----- src/admin/models.py | 4 ---- src/admin/router.py | 5 ++--- src/admin/schemas.py | 4 ---- src/admin/service.py | 8 -------- src/admin/utils.py | 8 -------- 9 files changed, 5 insertions(+), 50 deletions(-) diff --git a/src/admin/config.py b/src/admin/config.py index 3139962..46e4142 100644 --- a/src/admin/config.py +++ b/src/admin/config.py @@ -1,7 +1,3 @@ """ Configurations for the admin module - -Configurations: - - List: Description - - Configs: Description """ \ No newline at end of file diff --git a/src/admin/constants.py b/src/admin/constants.py index 58a3b2a..c75163f 100644 --- a/src/admin/constants.py +++ b/src/admin/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for the admin module - -Constants: - - List: Description - - Consts: Description +Constants for the admin module """ \ No newline at end of file diff --git a/src/admin/dependencies.py b/src/admin/dependencies.py index cb4c147..aff00b3 100644 --- a/src/admin/dependencies.py +++ b/src/admin/dependencies.py @@ -1,11 +1,3 @@ """ -Router dependencies for the admin module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Dependencies for the admin module """ \ No newline at end of file diff --git a/src/admin/exceptions.py b/src/admin/exceptions.py index 644a8b0..513805c 100644 --- a/src/admin/exceptions.py +++ b/src/admin/exceptions.py @@ -1,7 +1,3 @@ """ -Module specific exceptions for the admin module - -Exceptions: - - List: Description - - Exceptions: Description +Custom exceptions for the admin module """ \ No newline at end of file diff --git a/src/admin/models.py b/src/admin/models.py index 1b60920..304e336 100644 --- a/src/admin/models.py +++ b/src/admin/models.py @@ -1,7 +1,3 @@ """ Database models for the admin module - -Models: - - List: Description - - Models: Description """ \ No newline at end of file diff --git a/src/admin/router.py b/src/admin/router.py index 13ac897..e0246a4 100644 --- a/src/admin/router.py +++ b/src/admin/router.py @@ -1,9 +1,8 @@ """ Router endpoints for the admin module -Endpoints: - - List: Description - - Endpoints: Description +Exports: + - router: fastapi.APIRouter """ from fastapi import APIRouter diff --git a/src/admin/schemas.py b/src/admin/schemas.py index 4490920..1289bcb 100644 --- a/src/admin/schemas.py +++ b/src/admin/schemas.py @@ -1,7 +1,3 @@ """ Pydantic models for the admin module - -Models: - - List: Description - - Models: Description """ \ No newline at end of file diff --git a/src/admin/service.py b/src/admin/service.py index 1cd5069..1db3599 100644 --- a/src/admin/service.py +++ b/src/admin/service.py @@ -1,11 +1,3 @@ """ Module specific business logic for the admin module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description """ \ No newline at end of file diff --git a/src/admin/utils.py b/src/admin/utils.py index 4e316d1..e570f14 100644 --- a/src/admin/utils.py +++ b/src/admin/utils.py @@ -1,11 +1,3 @@ """ Non-business logic reusable functions and classes for the admin module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description """ \ No newline at end of file From a86cfea65aefb9ed5d048df78f6a1196d50d1ade Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 11:06:54 +0100 Subject: [PATCH 02/12] minor: type hint --- src/auth/dependencies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/auth/dependencies.py b/src/auth/dependencies.py index e85ef9c..0cc23a2 100644 --- a/src/auth/dependencies.py +++ b/src/auth/dependencies.py @@ -13,6 +13,7 @@ from typing import Annotated from fastapi import Depends from src.user.dependencies import user_model_claims_dependency +from src.user.models import User from src.organisation.dependencies import org_model_query_dependency, org_model_body_dependency from src.organisation.models import Organisation as Org @@ -69,4 +70,4 @@ async def user_model_super_admin(user_model: user_model_claims_dependency): raise UnauthorizedException() -super_admin_dependency = Annotated[bool, Depends(user_model_super_admin)] +super_admin_dependency = Annotated[type[User], Depends(user_model_super_admin)] From 42349b01824fd84bf247d87fee8aee15976558cc Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 11:10:55 +0100 Subject: [PATCH 03/12] docs: auth docstrings Issue: #13 --- src/auth/config.py | 7 +++---- src/auth/constants.py | 6 +----- src/auth/dependencies.py | 14 ++++++-------- src/auth/exceptions.py | 5 ++--- src/auth/models.py | 6 +----- src/auth/router.py | 6 +++--- src/auth/schemas.py | 6 +----- src/auth/service.py | 4 ++-- src/auth/utils.py | 10 +--------- 9 files changed, 20 insertions(+), 44 deletions(-) diff --git a/src/auth/config.py b/src/auth/config.py index 37de6a7..82646d2 100644 --- a/src/auth/config.py +++ b/src/auth/config.py @@ -1,9 +1,8 @@ """ -Configurations for auth module, import auth_settings +Configurations for the auth module -Configurations: - - List: Description - - Configs: Description +Exports: + - auth_settings: Contains OIDC information """ from src.config import CustomBaseSettings diff --git a/src/auth/constants.py b/src/auth/constants.py index 1ab3fd6..faabd82 100644 --- a/src/auth/constants.py +++ b/src/auth/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for auth module - -Constants: - - List: Description - - Consts: Description +Constants for the auth module """ \ No newline at end of file diff --git a/src/auth/dependencies.py b/src/auth/dependencies.py index 0cc23a2..f87064a 100644 --- a/src/auth/dependencies.py +++ b/src/auth/dependencies.py @@ -1,13 +1,11 @@ """ -Router dependencies for auth module +Auth dependencies -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Exports: + - org_query_user_claims_dependency: bool: Verifies user belongs to org + - org_model_root_claim_query_dependency: org_model: verifies org exists and user is either root or su, gets org from query + - org_model_root_claim_body_dependency: org_model: verifies org exists and user is either root or su, gets org from body + - super_admin_dependency: user_model: verifies the user is a super admin """ from typing import Annotated from fastapi import Depends diff --git a/src/auth/exceptions.py b/src/auth/exceptions.py index 71aede1..613b166 100644 --- a/src/auth/exceptions.py +++ b/src/auth/exceptions.py @@ -1,9 +1,8 @@ """ -Module specific exceptions for auth module +Module specific exceptions for the auth module Exceptions: - - List: Description - - Exceptions: Description + - UnauthorizedException: Takes an optional message string """ from typing import Optional diff --git a/src/auth/models.py b/src/auth/models.py index 487b3d1..4717477 100644 --- a/src/auth/models.py +++ b/src/auth/models.py @@ -1,7 +1,3 @@ """ -Database models for auth module - -Models: - - List: Description - - Models: Description +Database models for the auth module """ \ No newline at end of file diff --git a/src/auth/router.py b/src/auth/router.py index 5e8871d..9cd7fad 100644 --- a/src/auth/router.py +++ b/src/auth/router.py @@ -1,8 +1,8 @@ """ -Router endpoints for auth module -Contains oauth registration +Router endpoints for the auth module -Endpoints: +Exports: + - router: fastapi.APIRouter """ from fastapi import APIRouter diff --git a/src/auth/schemas.py b/src/auth/schemas.py index 92afae5..279bb1b 100644 --- a/src/auth/schemas.py +++ b/src/auth/schemas.py @@ -1,7 +1,3 @@ """ -Pydantic models for auth module - -Models: - - List: Description - - Models: Description +Pydantic models for the auth module """ \ No newline at end of file diff --git a/src/auth/service.py b/src/auth/service.py index e0a764e..dce8217 100644 --- a/src/auth/service.py +++ b/src/auth/service.py @@ -1,8 +1,8 @@ """ -Module specific business logic for auth module +Module specific business logic for the auth module Exports: - - claims_dependency + - claims_dependency: Dict[str, Any] containing OIDC claims and database ID """ import json import requests diff --git a/src/auth/utils.py b/src/auth/utils.py index e913437..ed66e7c 100644 --- a/src/auth/utils.py +++ b/src/auth/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for auth module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the auth module """ \ No newline at end of file From 39d3f2d5607688d0b7100432340a1bc4d144518d Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 11:22:12 +0100 Subject: [PATCH 04/12] docs: contact docstrings Issue: #13 --- src/contact/config.py | 6 +----- src/contact/constants.py | 6 +----- src/contact/dependencies.py | 10 +--------- src/contact/exceptions.py | 7 +++---- src/contact/models.py | 4 ++-- src/contact/router.py | 10 +--------- src/contact/schemas.py | 6 +++--- src/contact/service.py | 10 +--------- src/contact/utils.py | 10 +--------- 9 files changed, 14 insertions(+), 55 deletions(-) diff --git a/src/contact/config.py b/src/contact/config.py index 5e7f864..2253a68 100644 --- a/src/contact/config.py +++ b/src/contact/config.py @@ -1,7 +1,3 @@ """ -Configurations for contact module - -Configurations: - - List: Description - - Configs: Description +Configurations for the contact module """ \ No newline at end of file diff --git a/src/contact/constants.py b/src/contact/constants.py index 49898d6..41f6ded 100644 --- a/src/contact/constants.py +++ b/src/contact/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for contact module - -Constants: - - List: Description - - Consts: Description +Constants for the contact module """ \ No newline at end of file diff --git a/src/contact/dependencies.py b/src/contact/dependencies.py index 2450730..de1d404 100644 --- a/src/contact/dependencies.py +++ b/src/contact/dependencies.py @@ -1,11 +1,3 @@ """ -Router dependencies for contact module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Dependencies for the contact module """ \ No newline at end of file diff --git a/src/contact/exceptions.py b/src/contact/exceptions.py index b3f8e11..6710bf3 100644 --- a/src/contact/exceptions.py +++ b/src/contact/exceptions.py @@ -1,9 +1,8 @@ """ -Module specific exceptions for contact module +Exceptions related to the contact module -Exceptions: - - List: Description - - Exceptions: Description +Exports: + - ContactNotFoundException: Takes an optional contact ID int """ from typing import Optional diff --git a/src/contact/models.py b/src/contact/models.py index e3d0d05..3369501 100644 --- a/src/contact/models.py +++ b/src/contact/models.py @@ -1,9 +1,9 @@ """ -Database models for contact module +Database models for the contact module Models: - Contact: id[pk], email, first_name, last_name, phonenumber, vat_number - street_address, post_office_box_number, address_locality, country_code, address_region, postal_code + street_address, street_address_line_2, post_office_box_number, address_locality, country_code, address_region, postal_code """ from sqlalchemy import Column, Integer, String, ForeignKey diff --git a/src/contact/router.py b/src/contact/router.py index 9528fd9..cdab37f 100644 --- a/src/contact/router.py +++ b/src/contact/router.py @@ -1,13 +1,5 @@ """ -Router endpoints for contact module - -Endpoints: - - [get]/{contact_id} - Returns non-address type details for contact - - [get]/{contact_id}/address - Returns address details for contact - - [get]/{contact_id}/orgs - Returns a list of orgs which the contact is assigned to, and what they are assigned as - - [post]/ - Creates a new contact - - [patch]/{contact_id} - Updates the details of an existing contact - - [delete]/{contact_id} - Deletes a contact by ID +Router endpoints for the contact module """ from fastapi import APIRouter diff --git a/src/contact/schemas.py b/src/contact/schemas.py index b5103f0..b008739 100644 --- a/src/contact/schemas.py +++ b/src/contact/schemas.py @@ -1,9 +1,9 @@ """ -Pydantic models for contact module +Pydantic models for the contact module Models: - - List: Description - - Models: Description + - ContactAddress + - ContactModel: Contains ContactAddress as a property """ from typing import Optional diff --git a/src/contact/service.py b/src/contact/service.py index ee4af6c..e04866a 100644 --- a/src/contact/service.py +++ b/src/contact/service.py @@ -1,11 +1,3 @@ """ -Module specific business logic for contact module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Module specific business logic for the contact module """ \ No newline at end of file diff --git a/src/contact/utils.py b/src/contact/utils.py index 74ec5c5..6a1d14a 100644 --- a/src/contact/utils.py +++ b/src/contact/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for contact module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the contact module """ \ No newline at end of file From 0c1c9f62ee820808daaee09d06427d6b562f531f Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 13:19:54 +0100 Subject: [PATCH 05/12] fix: permission dependency --- src/iam/dependencies.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/iam/dependencies.py b/src/iam/dependencies.py index d5632c7..24ae1ab 100644 --- a/src/iam/dependencies.py +++ b/src/iam/dependencies.py @@ -15,7 +15,7 @@ from fastapi import Depends, Query from src.database import db_dependency -from src.iam.models import Group +from src.iam.models import Group, Permission from src.iam.exceptions import GroupNotFoundException, PermNotFoundException from src.iam.schemas import GroupIDMixin, PermIDMixin @@ -43,14 +43,14 @@ def get_group_model_body(db: db_dependency, request_model: Optional[GroupIDMixin group_model_body_dependency = Annotated[type[Group], Depends(get_group_model_body)] -def get_perm_model_body(db: db_dependency, request_model: Optional[PermIDMixin] = None) -> type[Group]: +def get_perm_model_body(db: db_dependency, request_model: Optional[PermIDMixin] = None) -> type[Permission]: perm_id = getattr(request_model, "permission_id", None) if perm_id is None: raise PermNotFoundException - group_model = db.get(Group, perm_id) - if group_model is None: + perm_model = db.get(Permission, perm_id) + if perm_model is None: raise PermNotFoundException(perm_id) - return group_model + return perm_model -perm_model_body_dependency = Annotated[type[Group], Depends(get_perm_model_body)] +perm_model_body_dependency = Annotated[type[Permission], Depends(get_perm_model_body)] From 0e169af45691ec0a7322e15d33e95664941c99d1 Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 13:22:24 +0100 Subject: [PATCH 06/12] docs: iam docstrings Issue: #13 --- src/iam/config.py | 6 +----- src/iam/constants.py | 6 +----- src/iam/dependencies.py | 13 +++++-------- src/iam/exceptions.py | 6 +++--- src/iam/models.py | 7 +++++-- src/iam/router.py | 16 +++++++++++++--- src/iam/schemas.py | 7 ++++--- src/iam/service.py | 5 +++-- src/iam/utils.py | 10 +--------- 9 files changed, 36 insertions(+), 40 deletions(-) diff --git a/src/iam/config.py b/src/iam/config.py index 4be170e..165dc07 100644 --- a/src/iam/config.py +++ b/src/iam/config.py @@ -1,7 +1,3 @@ """ -Configurations for - -Configurations: - - List: Description - - Configs: Description +Configurations for the IAM module """ \ No newline at end of file diff --git a/src/iam/constants.py b/src/iam/constants.py index e1df957..0dc94e7 100644 --- a/src/iam/constants.py +++ b/src/iam/constants.py @@ -1,7 +1,3 @@ """ -Constants and error codes for - -Constants: - - List: Description - - Consts: Description +Constants for the IAM module """ \ No newline at end of file diff --git a/src/iam/dependencies.py b/src/iam/dependencies.py index 24ae1ab..37b8e87 100644 --- a/src/iam/dependencies.py +++ b/src/iam/dependencies.py @@ -1,13 +1,10 @@ """ -Router dependencies for the IAM module +Dependencies for the IAM module -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Exports: + - group_model_query_dependency: group_model: Gets group model from db, if it exists. Uses group_id from query param. + - group_model_body_dependency: group_model: Gets group model from db, if it exists. Uses group_id from request body. + - perm_model_body_dependency: perm_model: Gets perm model from db, if it exists. Uses perm_id from request body. """ from typing import Annotated, Optional diff --git a/src/iam/exceptions.py b/src/iam/exceptions.py index 5c97b9a..84a77ed 100644 --- a/src/iam/exceptions.py +++ b/src/iam/exceptions.py @@ -1,9 +1,9 @@ """ -Module specific exceptions for the IAM module +Exceptions related to the IAM module Exceptions: - - List: Description - - Exceptions: Description + - GroupNotFoundException: Takes an optional group_id int + - PermNotFoundException: Takes an optional perm_id int """ from typing import Optional diff --git a/src/iam/models.py b/src/iam/models.py index 83d1ae2..ea12c0f 100644 --- a/src/iam/models.py +++ b/src/iam/models.py @@ -2,8 +2,11 @@ Database models for the IAM module Models: - - List: Description - - Models: Description + - Permission: + - id[PK], resource[U1], action[U1], service_id[FK][U1] + - service_rel: ORM relationship over service_id FK + - group_rel: ORM relationship backpops to Group.permission_rel + - service_name: Calc property service_rel.name """ from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint from sqlalchemy.orm import relationship diff --git a/src/iam/router.py b/src/iam/router.py index 4b2bbb4..feb4f3e 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -1,9 +1,19 @@ """ -Router endpoints for +Router endpoints for IAM Endpoints: - - List: Description - - Endpoints: Description + - [POST](/iam/can_act_on_resource): [API key & user claim]: Service access point to verify user permissions + - [GET](/iam/group/permissions): [root user]: Gets list of perms(service, resource, action) the given group(id) has + - [DELETE](/iam/group/permissions): [root user]: Removes a given perm(id) from the given group(id) + - [GET](/iam/group/users): [root user]: Gets a list of users(id, name, email) that are assigned to the given group(id) + - [POST](/iam/group): [root user]: Creates a new group for the given org(id) + - [PUT](/iam/group/permission): [root user]: Assigns a perm(id) to the given group(id) + - [PUT](/iam/group/user): [root user]: Assigns a user(id) to a group(id) + - [DELETE](/iam/group/user): [root user]: Removes a user(id) from the given group(id) + - [GET](/iam/permissions): [root user]: Gets a list of all permissions + - [POST](/iam/permission): [super admin]: Creates a new permission + - [DELETE](/iam/permission): [super admin]: Removes a permission + - [GET](/iam/permissions/search): [root user]: Returns a list of permissions matching a filter(service|resource|action) """ from fastapi import APIRouter, status from sqlalchemy.exc import IntegrityError diff --git a/src/iam/schemas.py b/src/iam/schemas.py index f6cd7bb..1b1d4d2 100644 --- a/src/iam/schemas.py +++ b/src/iam/schemas.py @@ -1,9 +1,10 @@ """ Pydantic models for the IAM module -Models: - - List: Description - - Models: Description +Models follow the nomenclature of: +- Sub-models: "Response" +- Mixins: "Mixin" +- Models: "" ie "IAMGetGroupPermissionsResponse" """ from typing import Optional diff --git a/src/iam/service.py b/src/iam/service.py index 1607cd0..b1d416b 100644 --- a/src/iam/service.py +++ b/src/iam/service.py @@ -1,7 +1,8 @@ """ -Module specific business logic for +Business logic reusable functions related to IAM -Exports service_key_dependency +Exports: + - service_key_dependency: bool: verifies request headers contain the correct api key for the service """ from typing import Annotated diff --git a/src/iam/utils.py b/src/iam/utils.py index 5afbb54..d948f09 100644 --- a/src/iam/utils.py +++ b/src/iam/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the IAM module """ From 7a0f43d34fddc8788522b0cdd9e468e0a7e573cd Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 13:28:14 +0100 Subject: [PATCH 07/12] minor: org schema nomenclature --- src/organisation/schemas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/organisation/schemas.py b/src/organisation/schemas.py index d727b98..299f48f 100644 --- a/src/organisation/schemas.py +++ b/src/organisation/schemas.py @@ -16,7 +16,7 @@ from src.user.schemas import UserIDMixin from src.organisation.constants import Status, ContactType -class OrgQuestionnaire(CustomBaseModel): +class Questionnaire(CustomBaseModel): question_one: str question_two: str question_three: str @@ -27,10 +27,10 @@ class OrgIDMixin(CustomBaseModel): class OrgPostOrgRequest(CustomBaseModel): name: str - intake_questionnaire: Optional[OrgQuestionnaire] = None + intake_questionnaire: Optional[Questionnaire] = None class OrgPatchQuestionnaireRequest(OrgIDMixin): - intake_questionnaire: OrgQuestionnaire + intake_questionnaire: Questionnaire partial: bool class OrgPatchStatusRequest(OrgIDMixin): From 9a1975c389fe18d3fca4c932cf49580096d1bd6d Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 13:37:32 +0100 Subject: [PATCH 08/12] minor: iam schema nomenclature --- src/iam/router.py | 18 +++++++++--------- src/iam/schemas.py | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/iam/router.py b/src/iam/router.py index feb4f3e..a6398a9 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -35,10 +35,10 @@ from src.iam.service import service_key_dependency from src.iam.models import Permission as Perm, GroupPermissions as GPerms, Group, UserGroups from src.iam.dependencies import group_model_query_dependency, group_model_body_dependency, perm_model_body_dependency from src.iam.schemas import IAMGetGroupPermissionsResponse, IAMGetGroupUsersResponse, IAMPostGroupRequest, \ - GroupResponse, IAMPostGroupResponse, IAMPutGroupPermissionRequest, IAMPutGroupPermissionResponse, \ + GroupSchema, IAMPostGroupResponse, IAMPutGroupPermissionRequest, IAMPutGroupPermissionResponse, \ IAMPutGroupUserRequest, IAMPutGroupUserResponse, IAMDeleteGroupPermissionRequest, IAMDeleteGroupPermissionResponse, \ IAMDeleteGroupUserRequest, IAMDeleteGroupUserResponse, IAMGetPermissionsResponse, IAMPostPermissionRequest, \ - IAMPostPermissionResponse, PermissionResponse, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse + IAMPostPermissionResponse, PermissionSchema, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse router = APIRouter( tags=["IAM"], @@ -101,7 +101,7 @@ async def create_group(db: db_dependency, org_model: org_model_root_claim_body_d except IntegrityError as e: if isinstance(e.orig, errors.UniqueViolation): raise Conflict("Group with this name already exists") - response = GroupResponse(**group_model.__dict__) + response = GroupSchema(**group_model.__dict__) db.commit() return {"group": response} @@ -117,7 +117,7 @@ async def add_group_permission(db: db_dependency, group_model: group_model_body_ group_model.permission_rel.append(perm_model) db.flush() - response = IAMPutGroupPermissionResponse(group=GroupResponse(**group_model.__dict__), permissions=group_model.permission_rel) + response = IAMPutGroupPermissionResponse(group=GroupSchema(**group_model.__dict__), permissions=group_model.permission_rel) db.commit() return response @@ -132,7 +132,7 @@ async def add_group_user(db: db_dependency, group_model: group_model_body_depend group_model.user_rel.append(user_model) db.flush() - response = IAMPutGroupUserResponse(group=GroupResponse(**group_model.__dict__), users=group_model.user_rel) + response = IAMPutGroupUserResponse(group=GroupSchema(**group_model.__dict__), users=group_model.user_rel) db.commit() return response @@ -144,8 +144,8 @@ async def remove_group_permissions(db: db_dependency, group_model: group_model_b group_model.permission_rel.remove(perm_model) db.flush() - response = IAMDeleteGroupPermissionResponse(group=GroupResponse(**group_model.__dict__), - permissions=group_model.permission_rel) + response = IAMDeleteGroupPermissionResponse(group=GroupSchema(**group_model.__dict__), + permissions=group_model.permission_rel) db.commit() return response @@ -157,7 +157,7 @@ async def remove_group_user(db: db_dependency, group_model: group_model_body_dep user_model.group_rel.remove(group_model) db.flush() - response = IAMDeleteGroupUserResponse(group=GroupResponse(**group_model.__dict__), users=group_model.user_rel) + response = IAMDeleteGroupUserResponse(group=GroupSchema(**group_model.__dict__), users=group_model.user_rel) db.commit() return response @@ -179,7 +179,7 @@ async def create_new_permission(db: db_dependency, su: super_admin_dependency, r if isinstance(e.orig, errors.UniqueViolation): raise Conflict(message="Permission already exists") db.flush() - response = IAMPostPermissionResponse(permission=PermissionResponse(**perm_model.__dict__)) + response = IAMPostPermissionResponse(permission=PermissionSchema(**perm_model.__dict__)) db.commit() return response diff --git a/src/iam/schemas.py b/src/iam/schemas.py index 1b1d4d2..3f34390 100644 --- a/src/iam/schemas.py +++ b/src/iam/schemas.py @@ -2,7 +2,7 @@ Pydantic models for the IAM module Models follow the nomenclature of: -- Sub-models: "Response" +- Sub-models: "Schema" - Mixins: "Mixin" - Models: "" ie "IAMGetGroupPermissionsResponse" """ @@ -15,20 +15,20 @@ from src.schemas import CustomBaseModel from user.schemas import UserIDMixin -class UserResponse(CustomBaseModel): +class UserSchema(CustomBaseModel): id: int first_name: str last_name: str email: EmailStr -class PermissionResponse(CustomBaseModel): +class PermissionSchema(CustomBaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") service_name: str resource: str action: str -class GroupResponse(CustomBaseModel): +class GroupSchema(CustomBaseModel): id: int name: str @@ -39,47 +39,47 @@ class PermIDMixin(CustomBaseModel): permission_id: int class IAMGetGroupPermissionsResponse(CustomBaseModel): - permissions: list[PermissionResponse] + permissions: list[PermissionSchema] class IAMGetGroupUsersResponse(CustomBaseModel): - users : list[UserResponse] + users : list[UserSchema] class IAMPostGroupRequest(OrgIDMixin): name: str class IAMPostGroupResponse(CustomBaseModel): - group: GroupResponse + group: GroupSchema class IAMPutGroupPermissionRequest(GroupIDMixin, PermIDMixin): pass class IAMPutGroupPermissionResponse(CustomBaseModel): - group: GroupResponse - permissions: list[PermissionResponse] + group: GroupSchema + permissions: list[PermissionSchema] class IAMPutGroupUserRequest(GroupIDMixin, UserIDMixin): pass class IAMPutGroupUserResponse(CustomBaseModel): - group: GroupResponse - users: list[UserResponse] + group: GroupSchema + users: list[UserSchema] class IAMDeleteGroupPermissionRequest(GroupIDMixin, PermIDMixin): pass class IAMDeleteGroupPermissionResponse(CustomBaseModel): - group: GroupResponse - permissions: list[PermissionResponse] + group: GroupSchema + permissions: list[PermissionSchema] class IAMDeleteGroupUserRequest(GroupIDMixin, UserIDMixin): pass class IAMDeleteGroupUserResponse(CustomBaseModel): - group: GroupResponse - users: list[UserResponse] + group: GroupSchema + users: list[UserSchema] class IAMGetPermissionsResponse(CustomBaseModel): - permissions: list[PermissionResponse] + permissions: list[PermissionSchema] class IAMPostPermissionRequest(CustomBaseModel): service_id: int @@ -87,7 +87,7 @@ class IAMPostPermissionRequest(CustomBaseModel): action: str class IAMPostPermissionResponse(CustomBaseModel): - permission: PermissionResponse + permission: PermissionSchema class IAMDeletePermissionRequest(PermIDMixin): pass @@ -98,4 +98,4 @@ class IAMGetPermissionsSearchRequest(CustomBaseModel): action: Optional[str] = None class IAMGetPermissionsSearchResponse(CustomBaseModel): - permissions: list[PermissionResponse] + permissions: list[PermissionSchema] From 82c2b13a7f3bdf5971138c2157fbe006f158af4f Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 14:05:31 +0100 Subject: [PATCH 09/12] minor: contact endpoints allowed preapproval --- src/organisation/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/organisation/dependencies.py b/src/organisation/dependencies.py index 3edd8be..b73629b 100644 --- a/src/organisation/dependencies.py +++ b/src/organisation/dependencies.py @@ -26,7 +26,7 @@ def get_org_model(db, request: Request, org_id: int): if org_model is None: raise OrgNotFoundException(org_id) - pre_approval_endpoints = ["PATCH/org/status", "PATCH/org/questionnaire", "GET/org/id"] + pre_approval_endpoints = ["PATCH/org/status", "PATCH/org/questionnaire", "GET/org/id", "GET/org/contact", "PATCH/org/contact"] current_request = f"{request.method}{request.url.path}" if current_request not in pre_approval_endpoints and org_model.status != OrgStatus.APPROVED: raise AwaitingApprovalException(org_id) From 33e78d4a9be08c96e732d1dedaece38158f9a66c Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 14:23:36 +0100 Subject: [PATCH 10/12] docs: org docstrings issue: #13 --- src/organisation/config.py | 6 +----- src/organisation/constants.py | 2 +- src/organisation/dependencies.py | 12 ++++-------- src/organisation/exceptions.py | 6 +++--- src/organisation/models.py | 13 ++++++++++--- src/organisation/router.py | 21 ++++++++++++--------- src/organisation/schemas.py | 7 ++++--- src/organisation/service.py | 10 +--------- src/organisation/utils.py | 10 +--------- 9 files changed, 37 insertions(+), 50 deletions(-) diff --git a/src/organisation/config.py b/src/organisation/config.py index 51f5dd9..e24ca5b 100644 --- a/src/organisation/config.py +++ b/src/organisation/config.py @@ -1,7 +1,3 @@ """ -Configurations for organisation module - -Configurations: - - List: Description - - Configs: Description +Configurations for the organisation module """ \ No newline at end of file diff --git a/src/organisation/constants.py b/src/organisation/constants.py index 6cae1fd..ced0682 100644 --- a/src/organisation/constants.py +++ b/src/organisation/constants.py @@ -1,5 +1,5 @@ """ -Constants and error codes for organisation module +Constants for the organisation module Classes: - Status(StrEnum): PARTIAL, SUBMITTED, REMEDIATION, APPROVED, REJECTED, REMOVED diff --git a/src/organisation/dependencies.py b/src/organisation/dependencies.py index b73629b..51ee569 100644 --- a/src/organisation/dependencies.py +++ b/src/organisation/dependencies.py @@ -1,13 +1,9 @@ """ -Router dependencies for organisation module +Dependencies related to the organisation module -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Exports: + - org_model_query_dependency: org_model: Gets org model from db, if it exists. Uses org_id from query param. Also verifies if the org has been approved. + - org_model_body_dependency: org_model: Gets org model from db, if it exists. Uses org_id from request body. Also verifies if the org has been approved. """ from typing import Annotated, Optional diff --git a/src/organisation/exceptions.py b/src/organisation/exceptions.py index c7d6a0c..8fe61cc 100644 --- a/src/organisation/exceptions.py +++ b/src/organisation/exceptions.py @@ -1,9 +1,9 @@ """ -Module specific exceptions for organisation module +Exceptions related to the organisation module Exceptions: - - List: Description - - Exceptions: Description + - OrgNotFoundException: Takes an optional org_id int + - AwaitingApprovalException: Takes an optional org_id int """ from typing import Optional diff --git a/src/organisation/models.py b/src/organisation/models.py index f696824..3663f2c 100644 --- a/src/organisation/models.py +++ b/src/organisation/models.py @@ -2,9 +2,16 @@ Database models for organisation module Models: - - Organisation: id[pk], name, status, intake_questionnaire, - billing_contact_id[fk], security_contact_id[fk], owner_contact_id[fk] - - OrgUsers: org_id[fk][cpk], user_id[fk][cpk], is_admin + - Organisation: + - id[PK], name, status, intake_questionnaire, root_user_id[FK], billing_contact_id[FK], security_contact_id[FK], owner_contact_id[FK] + - user_rel: ORM relationship to User via OrgUsers relationship table + - group_rel: ORM relationship to Group, backprops Group.org_rel + - root_user_rel: ORM relationship to User with root_user_id FK + - root_user_email: Calc property root_user_rel.email + - billing_contact_rel: ORM relationship to Contact with billing_contact FK + - security_contact_rel: ORM relationship to Contact with security_contact FK + - owner_contact_rel: ORM relationship to Contact with owner_contact FK + - OrgUsers: org_id[FK][PK], user_id[FK][PK] """ from sqlalchemy import Column, Integer, String, ForeignKey, JSON from sqlalchemy.orm import relationship diff --git a/src/organisation/router.py b/src/organisation/router.py index c26bc2f..2e71fba 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -2,15 +2,18 @@ Router endpoints for organisation module Endpoints: - - [get]/id/{org_id} - Retrieves an organisation by its ID - - [post]/ - Creates a new organisation - - [patch]/{org_id}/questionnaire - Updates the questionnaire data for an organisation (can be partial or final submission) - - [patch]/{org_id}/status - Updates the status of an organisation - - [patch]/{org_id}/contact - Assigns a contact to an organisation (as billing, security, or owner) - - [get]/{org_id}/users - Retrieves all users associated with an organisation - - [post]/{org_id}/users - Adds a new user to an organisation - - [delete]/{org_id} - Deletes an organisation by ID - - [get]/{org_id}/contact/{contact_type} - Retrieves the contact of a specific type (owner, billing, security) for an organisation + - [GET](/org/id): [root user]: Get details about an organisation(id) + - [POST](/org/): [oidc claim]: Creates an organisation, adds the current user as a user and sets them to be the root user + - [PATCH](/org/questionnaire): [root user]: Updates the org's intake questionnaire and optionally be submitted for review + - [PATCH](/org/status): [super admin]: Allows a super admin to update an org(id) status(Status enum) + - [GET](/org/users): [root user]: Gets a list of the org(id) users(email) + - [POST](/org/users): [root user]: Adds a new user(id) to the org(id) + - [DELETE](/org/): [super admin]: Deletes an organisation(id) + - [PATCH](/org/root_user): [super admin]: Updates an org(id) root user(id) + - [GET](/org/groups): [root user]: Gets a list of the org(id) groups(name) + - [DELETE](/org/user): [root user]: Removes a user(id) from an org(id) + - [GET](/org/contact): [root user]: Gets the (contact_type) contact for an org(id) + - [PATCH](/org/contact): [root user]: Updates the (contact_type) contact for an org(id). Any number of details can be changed. """ from typing import Annotated, Optional diff --git a/src/organisation/schemas.py b/src/organisation/schemas.py index 299f48f..0fb13c5 100644 --- a/src/organisation/schemas.py +++ b/src/organisation/schemas.py @@ -1,9 +1,10 @@ """ Pydantic models for organisation module -Models: - - List: Description - - Models: Description +Models follow the nomenclature of: +- Sub-models: "Schema" +- Mixins: "Mixin" +- Models: "" ie "OrgPostOrgRequest" """ from typing import Optional diff --git a/src/organisation/service.py b/src/organisation/service.py index b401681..6d73399 100644 --- a/src/organisation/service.py +++ b/src/organisation/service.py @@ -1,11 +1,3 @@ """ -Module specific business logic for organisation module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Reusable business logic functions for the organisation module """ \ No newline at end of file diff --git a/src/organisation/utils.py b/src/organisation/utils.py index 32b2842..ead22ca 100644 --- a/src/organisation/utils.py +++ b/src/organisation/utils.py @@ -1,11 +1,3 @@ """ -Non-business logic reusable functions and classes for organisation module - -Classes: - - List: Description - - Classes: Description - -Functions: - - List: Description - - Functions: Description +Non-business logic reusable functions and classes for the organisation module """ \ No newline at end of file From 00dcf7ce3549bb731f45718a8d0a4352c5ea696d Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 14:27:14 +0100 Subject: [PATCH 11/12] minor: service schema nomenclature --- src/service/router.py | 6 +++--- src/service/schemas.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/service/router.py b/src/service/router.py index 9bef22b..f53d666 100644 --- a/src/service/router.py +++ b/src/service/router.py @@ -17,7 +17,7 @@ from src.service.models import Service from src.service.utils import generate_api_key from src.service.dependencies import service_model_body_dependency from src.service.schemas import ServiceGetServiceResponse, ServicePostServiceRequest, ServicePostServiceResponse, \ - ServiceWithKeyResponse, ServicePatchKeyResponse, ServicePatchKeyRequest, ServiceDeleteServiceRequest + ServiceWithKeySchema, ServicePatchKeyResponse, ServicePatchKeyRequest, ServiceDeleteServiceRequest router = APIRouter( tags=["Service"], @@ -42,7 +42,7 @@ async def register_service(db: db_dependency, su: super_admin_dependency, servic if isinstance(e.orig, UniqueViolation): raise Conflict(message="Service with this name already exists") db.commit() - response = ServiceWithKeyResponse(**service_model.__dict__) + response = ServiceWithKeySchema(**service_model.__dict__) db.commit() return {"service": response} @@ -52,7 +52,7 @@ async def regenerate_api_key(db: db_dependency, su: super_admin_dependency, serv service_model.api_key = key db.flush() - response = ServiceWithKeyResponse(**service_model.__dict__) + response = ServiceWithKeySchema(**service_model.__dict__) db.commit() return {"service": response} diff --git a/src/service/schemas.py b/src/service/schemas.py index deef4e3..d6f2882 100644 --- a/src/service/schemas.py +++ b/src/service/schemas.py @@ -12,29 +12,29 @@ from src.schemas import CustomBaseModel class ServiceIDMixin(CustomBaseModel): service_id: int -class ServiceResponse(CustomBaseModel): +class ServiceSchema(CustomBaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") id: int name: str -class ServiceWithKeyResponse(ServiceResponse): +class ServiceWithKeySchema(ServiceSchema): api_key: str class ServiceGetServiceResponse(CustomBaseModel): - services: list[ServiceResponse] + services: list[ServiceSchema] class ServicePostServiceRequest(CustomBaseModel): name: str class ServicePostServiceResponse(CustomBaseModel): - service: ServiceWithKeyResponse + service: ServiceWithKeySchema class ServicePatchKeyRequest(ServiceIDMixin): pass class ServicePatchKeyResponse(CustomBaseModel): - service: ServiceWithKeyResponse + service: ServiceWithKeySchema class ServiceDeleteServiceRequest(ServiceIDMixin): pass From 71f26a4c9b0e89562ea7124dead2b2fa54cffd1b Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 14:32:08 +0100 Subject: [PATCH 12/12] feat: example .env --- example.env | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 example.env diff --git a/example.env b/example.env new file mode 100644 index 0000000..b2ab4c9 --- /dev/null +++ b/example.env @@ -0,0 +1,10 @@ +SECRET_KEY="" +OIDC_CONFIG="https://sso.sr2.uk/realms/sr2/.well-known/openid-configuration" +OIDC_ISSUER="https://sso.sr2.uk/realms/sr2" +OIDC_AUDIENCE="account" +CLIENT_ID="" + +DATABASE_NAME="cloud-api" +DATABASE_PORT="5432" +DATABASE_HOSTNAME="localhost" +DATABASE_CREDENTIALS="user:password"