major rewrite, in terms of new db system

This commit is contained in:
Tiago Scholten 2026-06-24 21:05:22 +02:00
parent 33d3b9db80
commit 89c1bfe08f
21 changed files with 191 additions and 113 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ build/
dist/
wheels/
*.egg-info
.mypy_cache
# Virtual environments
.venv

5
pre-commit Normal file
View File

@ -0,0 +1,5 @@
set -o pipefail
uv run mypy src/
uv run ruff check "$@"
git diff --exit-code

View File

@ -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"]

View File

@ -0,0 +1,7 @@
from pydantic import BaseModel, UUID7
from .refs import CollectionName
class FiberBaseModel(BaseModel):
uuid: UUID7
collection_name: CollectionName

View File

@ -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

View File

@ -1,6 +0,0 @@
from pydantic import BaseModel
from .submodels import LoginInfo, LoginInfoResponse
class LoginModel(BaseModel):
login: LoginInfo | LoginInfoResponse

View File

@ -15,7 +15,7 @@ class Gender(StrEnum):
nonbinary = "n"
class HonoraryStatus(StrEnum):
merit = ("merit",)
merit = "merit"
honorary = "honorary"
class OnlineAlmanacField(StrEnum):

View File

@ -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
mail_token: str | None = None

View File

@ -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??

View File

@ -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

View File

@ -29,4 +29,4 @@ class PublicKey:
name: str
key_type: str
key: str
expires: date
expires: date

View File

@ -0,0 +1,7 @@
from .submodels import LoginInfo, LoginInfoResponse
from fiber_package.base import FiberBaseModel
class LoginModel(FiberBaseModel):
login: LoginInfo | LoginInfoResponse

View File

@ -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

View File

@ -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

View File

@ -1,6 +0,0 @@
from pydantic import BaseModel
from fiber_package.util import GroupList, EntityList
class RootGroupModel(BaseModel):
associations: GroupList
sys_admins: EntityList

View File

@ -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

View File

@ -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
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"]]

View File

@ -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

View File

@ -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