From 33d3b9db807c9a2b1398e321e15a6562f02fc058 Mon Sep 17 00:00:00 2001 From: Tiago Scholten Date: Wed, 24 Jun 2026 12:07:49 +0200 Subject: [PATCH 1/6] started workign on new db system --- src/fiber_package/refs.py | 23 +++++++++++++++++++++++ src/fiber_package/util.py | 9 --------- 2 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 src/fiber_package/refs.py delete mode 100644 src/fiber_package/util.py diff --git a/src/fiber_package/refs.py b/src/fiber_package/refs.py new file mode 100644 index 0000000..e2c7ece --- /dev/null +++ b/src/fiber_package/refs.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from pydantic import UUID7 +from typing import Literal + +@dataclass(slots=True) +class SystemRef(Ref): + uuid: UUID7 + collection: Literal["system"] = "system" + +class CommitteeRef(Ref): + uuid: UUID7 + collection: Literal["committee"] = "committee" + +Ref = SystemRef | CommitteeRef + +# TODO.... + + +EntityList = Annotated[list[UUID4], Field(default_factory=list)] +GroupList = Annotated[list[UUID4], Field(default_factory=list)] + +EntityRef = UUID4 +GroupRef = UUID4 \ No newline at end of file diff --git a/src/fiber_package/util.py b/src/fiber_package/util.py deleted file mode 100644 index 2c6c325..0000000 --- a/src/fiber_package/util.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Annotated -from pydantic import RootModel, Field, UUID4 - - -EntityList = Annotated[list[UUID4], Field(default_factory=list)] -GroupList = Annotated[list[UUID4], Field(default_factory=list)] - -EntityRef = UUID4 -GroupRef = UUID4 \ No newline at end of file -- 2.47.1 From 89c1bfe08f518d693d4aae21e95a7d91844042ff Mon Sep 17 00:00:00 2001 From: Tiago Scholten Date: Wed, 24 Jun 2026 21:05:22 +0200 Subject: [PATCH 2/6] major rewrite, in terms of new db system --- .gitignore | 1 + pre-commit | 5 ++ pyproject.toml | 28 +++++++++ src/fiber_package/base.py | 7 +++ src/fiber_package/committee/committee.py | 58 +++++++++++++++++++ .../{groups/committees => committee}/enums.py | 0 src/fiber_package/entities/system.py | 6 -- .../{entities => entity}/__init__.py | 0 .../{entities => entity}/enums.py | 2 +- .../{entities => entity}/member.py | 7 +-- .../{entities => entity}/non_members.py | 9 ++- .../{entities => entity}/person.py | 9 ++- .../{entities => entity}/submodels.py | 2 +- src/fiber_package/entity/system.py | 7 +++ src/fiber_package/groups/association.py | 12 ---- .../groups/committees/committee.py | 50 ---------------- src/fiber_package/groups/root.py | 6 -- .../{groups/committees => guild}/guild.py | 31 +++++----- src/fiber_package/refs.py | 43 ++++++++++---- src/fiber_package/system/association.py | 15 +++++ src/fiber_package/system/root.py | 6 ++ 21 files changed, 191 insertions(+), 113 deletions(-) create mode 100644 pre-commit create mode 100644 src/fiber_package/base.py create mode 100644 src/fiber_package/committee/committee.py rename src/fiber_package/{groups/committees => committee}/enums.py (100%) delete mode 100644 src/fiber_package/entities/system.py rename src/fiber_package/{entities => entity}/__init__.py (100%) rename src/fiber_package/{entities => entity}/enums.py (95%) rename src/fiber_package/{entities => entity}/member.py (79%) rename src/fiber_package/{entities => entity}/non_members.py (66%) rename src/fiber_package/{entities => entity}/person.py (76%) rename src/fiber_package/{entities => entity}/submodels.py (97%) create mode 100644 src/fiber_package/entity/system.py delete mode 100644 src/fiber_package/groups/association.py delete mode 100644 src/fiber_package/groups/committees/committee.py delete mode 100644 src/fiber_package/groups/root.py rename src/fiber_package/{groups/committees => guild}/guild.py (53%) create mode 100644 src/fiber_package/system/association.py create mode 100644 src/fiber_package/system/root.py diff --git a/.gitignore b/.gitignore index 505a3b1..f1e7339 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ dist/ wheels/ *.egg-info +.mypy_cache # Virtual environments .venv diff --git a/pre-commit b/pre-commit new file mode 100644 index 0000000..f7b3d81 --- /dev/null +++ b/pre-commit @@ -0,0 +1,5 @@ +set -o pipefail + +uv run mypy src/ +uv run ruff check "$@" +git diff --exit-code diff --git a/pyproject.toml b/pyproject.toml index 223c779..a9f0e33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,3 +17,31 @@ build-backend = "uv_build" dev = [ "ruff>=0.15.1", ] + +[tool.mypy] +disable_error_code = ["import-untyped"] + +[tool.ruff.lint] +select = [ + "E", # standard style checks + "F", # standard code checks + "FAST", # for fastAPI + "ANN", # for annotations + "B", # for common bug causes + "A", # for builtin variable shadowing + "PIE", # for unnecessary code + "SIM", # for simplifications + "TID", # for tidy improts + "TC" , # for type checking + #"ARG", # for unused arguments + #"TD", # checks for TODOs + "N", # naming conventions + "W", # whitespace warnings (use --fix) + "PLC", # common conventions + "PLE", # common errors + "PLR", # common refactors + "PLW", # common warnings + "UP", # for old style + "RUF", # other RUF specific rules +] +extend-ignore = ["TC", "PLR2004"] \ No newline at end of file diff --git a/src/fiber_package/base.py b/src/fiber_package/base.py new file mode 100644 index 0000000..26dfda4 --- /dev/null +++ b/src/fiber_package/base.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel, UUID7 + +from .refs import CollectionName + +class FiberBaseModel(BaseModel): + uuid: UUID7 + collection_name: CollectionName diff --git a/src/fiber_package/committee/committee.py b/src/fiber_package/committee/committee.py new file mode 100644 index 0000000..4b57b21 --- /dev/null +++ b/src/fiber_package/committee/committee.py @@ -0,0 +1,58 @@ +from pydantic import model_validator, UUID7 + +from fiber_package.refs import ( + CommitteeEditionRef, + CommitteeEditionRefList, + EntityRefList +) +from fiber_package.base import FiberBaseModel + +from typing import Self, Literal +from datetime import date + +from .enums import CommitteeType + +''' +Committees are groups of members for a definite period. +Every committee has a list of editions. +An edition is a definite group of members with functions and a timespan. +''' + +class CommitteeModel(FiberBaseModel): + collection_name: Literal["committee"] = "committee" + + full_name_en: str + full_name_nl: str + description_en: str = "" + description_nl: str = "" + committee_type: CommitteeType + + active: bool = True + + editions: CommitteeEditionRefList + current_edition: CommitteeEditionRef | None + + @model_validator(mode='after') + def current_edition_in_editions(self) -> Self: + if self.current_edition is None: + return self + uuid = self.current_edition.uuid + if uuid not in self.editions: + raise ValueError("Current edition must be in list of editions") + return self + +class CommitteeEditionModel(FiberBaseModel): + collection_name: Literal["committee_edition"] = "committee_edition" + + members: EntityRefList + functions: dict[UUID7, str] + + start_date: date + end_date: date | None # None means ongoing + + @model_validator(mode='after') + def functions_to_members_only(self) -> Self: + for uuid in self.functions: + if uuid not in self.members: + raise ValueError("Cannot only assign functions to committee members") + return self diff --git a/src/fiber_package/groups/committees/enums.py b/src/fiber_package/committee/enums.py similarity index 100% rename from src/fiber_package/groups/committees/enums.py rename to src/fiber_package/committee/enums.py diff --git a/src/fiber_package/entities/system.py b/src/fiber_package/entities/system.py deleted file mode 100644 index 80276af..0000000 --- a/src/fiber_package/entities/system.py +++ /dev/null @@ -1,6 +0,0 @@ -from pydantic import BaseModel - -from .submodels import LoginInfo, LoginInfoResponse - -class LoginModel(BaseModel): - login: LoginInfo | LoginInfoResponse \ No newline at end of file diff --git a/src/fiber_package/entities/__init__.py b/src/fiber_package/entity/__init__.py similarity index 100% rename from src/fiber_package/entities/__init__.py rename to src/fiber_package/entity/__init__.py diff --git a/src/fiber_package/entities/enums.py b/src/fiber_package/entity/enums.py similarity index 95% rename from src/fiber_package/entities/enums.py rename to src/fiber_package/entity/enums.py index 3d5ccc1..ba27d87 100644 --- a/src/fiber_package/entities/enums.py +++ b/src/fiber_package/entity/enums.py @@ -15,7 +15,7 @@ class Gender(StrEnum): nonbinary = "n" class HonoraryStatus(StrEnum): - merit = ("merit",) + merit = "merit" honorary = "honorary" class OnlineAlmanacField(StrEnum): diff --git a/src/fiber_package/entities/member.py b/src/fiber_package/entity/member.py similarity index 79% rename from src/fiber_package/entities/member.py rename to src/fiber_package/entity/member.py index 3499199..62ba032 100644 --- a/src/fiber_package/entities/member.py +++ b/src/fiber_package/entity/member.py @@ -1,13 +1,12 @@ -from typing import Literal from pydantic import Field from .non_members import FormerMemberModel -from .enums import Membership, OnlineAlmanacField +from .enums import OnlineAlmanacField from .submodels import Address, LoginInfo class MemberModel(FormerMemberModel): # requires that Membership equals member - membership: Literal[Membership.member] = Membership.member + # membership: Literal[Membership.member] = Membership.member login: LoginInfo address: Address @@ -23,4 +22,4 @@ class MemberModel(FormerMemberModel): online_almanac_fields: list[OnlineAlmanacField] = Field(default_factory=list) # IT stuff shell: str = "/bin/bash" # TODO: support other shells? - mail_token: str | None = None \ No newline at end of file + mail_token: str | None = None diff --git a/src/fiber_package/entities/non_members.py b/src/fiber_package/entity/non_members.py similarity index 66% rename from src/fiber_package/entities/non_members.py rename to src/fiber_package/entity/non_members.py index 7eb3856..d3a53ab 100644 --- a/src/fiber_package/entities/non_members.py +++ b/src/fiber_package/entity/non_members.py @@ -1,8 +1,6 @@ -from typing import Literal -from pydantic import Field from .person import PersonModel -from .enums import HonoraryStatus, Membership, Gender +from .enums import HonoraryStatus, Gender class FormerMemberModel(PersonModel): title: str | None = None @@ -13,12 +11,13 @@ class FormerMemberModel(PersonModel): # company emails? class AlumnModel(FormerMemberModel): - membership: Literal[Membership.alumn] = Membership.alumn + # membership: Literal[Membership.alumn] = Membership.alumn graduated: bool = False receive_spam_mail: bool = True class ExternalModel(PersonModel): - membership: Literal[Membership.extern] = Membership.extern + # membership: Literal[Membership.extern] = Membership.extern + pass # TODO... payment??? # TODO require login info?? diff --git a/src/fiber_package/entities/person.py b/src/fiber_package/entity/person.py similarity index 76% rename from src/fiber_package/entities/person.py rename to src/fiber_package/entity/person.py index 0befc52..d8f4a2c 100644 --- a/src/fiber_package/entities/person.py +++ b/src/fiber_package/entity/person.py @@ -1,10 +1,13 @@ -from pydantic import BaseModel, model_validator, EmailStr -from typing import Self +from pydantic import model_validator, EmailStr +from typing import Self, Literal +from fiber_package.base import FiberBaseModel from .enums import Membership, LanguageOption from .submodels import Address -class PersonModel(BaseModel): +class PersonModel(FiberBaseModel): + collection_name: Literal["entity"] = "entity" + membership: Membership first_name: str tussenvoegsels: str | None = None # only in Dutch names diff --git a/src/fiber_package/entities/submodels.py b/src/fiber_package/entity/submodels.py similarity index 97% rename from src/fiber_package/entities/submodels.py rename to src/fiber_package/entity/submodels.py index 37e52fb..3d56547 100644 --- a/src/fiber_package/entities/submodels.py +++ b/src/fiber_package/entity/submodels.py @@ -29,4 +29,4 @@ class PublicKey: name: str key_type: str key: str - expires: date \ No newline at end of file + expires: date diff --git a/src/fiber_package/entity/system.py b/src/fiber_package/entity/system.py new file mode 100644 index 0000000..f68b07a --- /dev/null +++ b/src/fiber_package/entity/system.py @@ -0,0 +1,7 @@ + +from .submodels import LoginInfo, LoginInfoResponse + +from fiber_package.base import FiberBaseModel + +class LoginModel(FiberBaseModel): + login: LoginInfo | LoginInfoResponse diff --git a/src/fiber_package/groups/association.py b/src/fiber_package/groups/association.py deleted file mode 100644 index d5fcea7..0000000 --- a/src/fiber_package/groups/association.py +++ /dev/null @@ -1,12 +0,0 @@ -from pydantic import BaseModel - -from fiber_package.util import EntityList, GroupList - -class AssociationModel(BaseModel): - full_name: str - members: EntityList - committees: GroupList - guilds: GroupList - # events: GroupList - # activities: GroupList - # mailings: GroupList \ No newline at end of file diff --git a/src/fiber_package/groups/committees/committee.py b/src/fiber_package/groups/committees/committee.py deleted file mode 100644 index ff071f8..0000000 --- a/src/fiber_package/groups/committees/committee.py +++ /dev/null @@ -1,50 +0,0 @@ -from pydantic import BaseModel, model_validator -from fiber_package.util import EntityList, GroupList, GroupRef - -from typing import Self -from datetime import date - -from .enums import CommitteeType - -''' -Committees are groups of members for a definite period. -Every committee has a list of editions. -An edition is a definite group of members with functions and a timespan. -''' - -class CommitteeModel(BaseModel): - full_name_EN: str - full_name_NL: str - description_EN: str = "" - description_NL: str = "" - committee_type: CommitteeType - - active: bool = True - - editions: GroupList - current_edition: GroupRef | None - - @model_validator(mode='after') - def current_edition_in_editions(self) -> Self: - uuid = self.current_edition - if uuid is None: - return self - if uuid not in self.editions: - raise ValueError("Current edition must be in list of editions") - return self - - # TODO... maybe require that an active board always has a current_edition that is non-empty? - -class CommitteeEditionModel(BaseModel): - members: EntityList - functions: dict[UUID4, str] - - start_date: date - end_date: date | None # None means ongoing - - @model_validator(mode='after') - def functions_to_members_only(self) -> Self: - for uuid in self.functions.keys(): - if uuid not in self.members: - raise ValueError("Cannot only assign functions to members") - return self \ No newline at end of file diff --git a/src/fiber_package/groups/root.py b/src/fiber_package/groups/root.py deleted file mode 100644 index 2d3bfbd..0000000 --- a/src/fiber_package/groups/root.py +++ /dev/null @@ -1,6 +0,0 @@ -from pydantic import BaseModel -from fiber_package.util import GroupList, EntityList - -class RootGroupModel(BaseModel): - associations: GroupList - sys_admins: EntityList \ No newline at end of file diff --git a/src/fiber_package/groups/committees/guild.py b/src/fiber_package/guild/guild.py similarity index 53% rename from src/fiber_package/groups/committees/guild.py rename to src/fiber_package/guild/guild.py index e21e107..03e2822 100644 --- a/src/fiber_package/groups/committees/guild.py +++ b/src/fiber_package/guild/guild.py @@ -1,9 +1,11 @@ -from pydantic import BaseModel, model_validator -from fiber_package.util import EntityList +from pydantic import model_validator +from fiber_package.refs import EntityRefList, EntityRef from datetime import date -from typing import Self +from typing import Self, Literal + +from fiber_package.base import FiberBaseModel ''' @@ -13,16 +15,18 @@ They have a list of members. Removing old members from last_active is done in fiber ''' -class GuildModel(BaseModel): - full_name_EN: str - full_name_NL: str - description_EN: str - description_NL: str +class GuildModel(FiberBaseModel): + collection_name: Literal["guild"] = "guild" - members: EntityList - previous_members: EntityList + full_name_en: str + full_name_nl: str + description_en: str + description_nl: str - guild_masters: EntityList + members: EntityRefList + previous_members: EntityRefList + + guild_masters: EntityRefList last_active: dict[EntityRef, date] @@ -30,7 +34,9 @@ class GuildModel(BaseModel): def current_members_not_previous(self) -> Self: for member in self.members: if member in self.previous_members: - raise ValueError("There can be no overlap between members and previous members!") + raise ValueError( + "There can be no overlap between members and previous members!" + ) return self @model_validator(mode='after') @@ -39,4 +45,3 @@ class GuildModel(BaseModel): if master not in self.members: raise ValueError("All guild masters must be members!") return self - \ No newline at end of file diff --git a/src/fiber_package/refs.py b/src/fiber_package/refs.py index e2c7ece..3088c69 100644 --- a/src/fiber_package/refs.py +++ b/src/fiber_package/refs.py @@ -1,23 +1,42 @@ from dataclasses import dataclass from pydantic import UUID7 -from typing import Literal +from typing import Literal, TypeVar +from collections.abc import Iterator + +''' +CollectionName provides the list of all allowed collections! +''' +CollectionName = Literal["system", "entity", "committee", "committee_edition", "guild"] + +C = TypeVar("C", bound=str) @dataclass(slots=True) -class SystemRef(Ref): +class Ref[C: str]: uuid: UUID7 - collection: Literal["system"] = "system" + collection_name: C -class CommitteeRef(Ref): - uuid: UUID7 - collection: Literal["committee"] = "committee" +@dataclass(slots=True) +class RefList[C: str]: + uuids: list[UUID7] + collection_name: C -Ref = SystemRef | CommitteeRef + def __iter__(self) -> Iterator[UUID7]: + return iter(self.uuids) -# TODO.... + def __contains__(self, item: UUID7) -> bool: + return item in self.uuids +SystemRef = Ref[Literal["system"]] +SystemRefList = RefList[Literal["system"]] -EntityList = Annotated[list[UUID4], Field(default_factory=list)] -GroupList = Annotated[list[UUID4], Field(default_factory=list)] +EntityRef = Ref[Literal["entity"]] +EntityRefList = RefList[Literal["entity"]] -EntityRef = UUID4 -GroupRef = UUID4 \ No newline at end of file +CommitteeRef = Ref[Literal["committee"]] +CommitteeRefList = RefList[Literal["committee"]] + +CommitteeEditionRef = Ref[Literal["committee_edition"]] +CommitteeEditionRefList = RefList[Literal["committee_edition"]] + +GuildRef = Ref[Literal["guild"]] +GuildRefList = RefList[Literal["guild"]] diff --git a/src/fiber_package/system/association.py b/src/fiber_package/system/association.py new file mode 100644 index 0000000..857fcdf --- /dev/null +++ b/src/fiber_package/system/association.py @@ -0,0 +1,15 @@ +from fiber_package.base import FiberBaseModel +from fiber_package.refs import EntityRefList, CommitteeRefList, GuildRefList + + +class AssociationModel(FiberBaseModel): + full_name: str + members: EntityRefList + committees: CommitteeRefList + guilds: GuildRefList + + + # TODO: also add former_members, alumns, etc... ? + # events: GroupList + # activities: GroupList + # mailings: GroupList diff --git a/src/fiber_package/system/root.py b/src/fiber_package/system/root.py new file mode 100644 index 0000000..7b8bd6c --- /dev/null +++ b/src/fiber_package/system/root.py @@ -0,0 +1,6 @@ +from fiber_package.base import FiberBaseModel +from fiber_package.refs import SystemRefList, EntityRefList + +class RootGroupModel(FiberBaseModel): + associations: SystemRefList + sys_admins: EntityRefList -- 2.47.1 From 93f196b4caf75dd44bf758e1bd276fecac3c6de1 Mon Sep 17 00:00:00 2001 From: Tiago Scholten Date: Thu, 25 Jun 2026 20:33:51 +0200 Subject: [PATCH 3/6] rename --- src/fiber_package/entity/{system.py => login.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/fiber_package/entity/{system.py => login.py} (100%) diff --git a/src/fiber_package/entity/system.py b/src/fiber_package/entity/login.py similarity index 100% rename from src/fiber_package/entity/system.py rename to src/fiber_package/entity/login.py -- 2.47.1 From 1f8e33e53c50ac2947296ffffe91f3b20024b3a3 Mon Sep 17 00:00:00 2001 From: Tiago Scholten Date: Thu, 25 Jun 2026 20:59:42 +0200 Subject: [PATCH 4/6] testing... --- src/fiber_package/entity/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fiber_package/entity/login.py b/src/fiber_package/entity/login.py index f68b07a..497bb98 100644 --- a/src/fiber_package/entity/login.py +++ b/src/fiber_package/entity/login.py @@ -4,4 +4,4 @@ from .submodels import LoginInfo, LoginInfoResponse from fiber_package.base import FiberBaseModel class LoginModel(FiberBaseModel): - login: LoginInfo | LoginInfoResponse + login: LoginInfo # | LoginInfoResponse -- 2.47.1 From 7ed69677fc5a35cc8337cf3d471f11f169ece508 Mon Sep 17 00:00:00 2001 From: Tiago Scholten Date: Thu, 25 Jun 2026 21:00:20 +0200 Subject: [PATCH 5/6] testing... 2 --- src/fiber_package/entity/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fiber_package/entity/login.py b/src/fiber_package/entity/login.py index 497bb98..f68b07a 100644 --- a/src/fiber_package/entity/login.py +++ b/src/fiber_package/entity/login.py @@ -4,4 +4,4 @@ from .submodels import LoginInfo, LoginInfoResponse from fiber_package.base import FiberBaseModel class LoginModel(FiberBaseModel): - login: LoginInfo # | LoginInfoResponse + login: LoginInfo | LoginInfoResponse -- 2.47.1 From a46308a1569cdbb9ca4e02636813ebf15344772d Mon Sep 17 00:00:00 2001 From: Tiago Scholten Date: Thu, 25 Jun 2026 21:03:03 +0200 Subject: [PATCH 6/6] testing... 3 --- src/fiber_package/entity/submodels.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/fiber_package/entity/submodels.py b/src/fiber_package/entity/submodels.py index 3d56547..8aefb84 100644 --- a/src/fiber_package/entity/submodels.py +++ b/src/fiber_package/entity/submodels.py @@ -1,17 +1,20 @@ from dataclasses import dataclass, field from datetime import date -# always use @dataclass with slots=True for better performance +from pydantic import BaseModel -@dataclass(slots=True) -class LoginInfo: + + +# always use @dataclass with slots=True for better performance +# unless if you have unions like LoginInfo and Login... + +class LoginInfo(BaseModel): username: str hashed_password: str refresh_tokens: list[str] = field(default_factory=list) disabled: bool = False -@dataclass(slots=True) -class LoginInfoResponse: +class LoginInfoResponse(BaseModel): username: str @dataclass(slots=True) -- 2.47.1