tiago/db_structure #2
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
wheels/
|
wheels/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
.mypy_cache
|
||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv
|
.venv
|
||||||
|
|||||||
5
pre-commit
Normal file
5
pre-commit
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
uv run mypy src/
|
||||||
|
uv run ruff check "$@"
|
||||||
|
git diff --exit-code
|
||||||
@ -17,3 +17,31 @@ build-backend = "uv_build"
|
|||||||
dev = [
|
dev = [
|
||||||
"ruff>=0.15.1",
|
"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"]
|
||||||
7
src/fiber_package/base.py
Normal file
7
src/fiber_package/base.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from pydantic import BaseModel, UUID7
|
||||||
|
|
||||||
|
from .refs import CollectionName
|
||||||
|
|
||||||
|
class FiberBaseModel(BaseModel):
|
||||||
|
uuid: UUID7
|
||||||
|
collection_name: CollectionName
|
||||||
58
src/fiber_package/committee/committee.py
Normal file
58
src/fiber_package/committee/committee.py
Normal 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
|
||||||
@ -1,6 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from .submodels import LoginInfo, LoginInfoResponse
|
|
||||||
|
|
||||||
class LoginModel(BaseModel):
|
|
||||||
login: LoginInfo | LoginInfoResponse
|
|
||||||
@ -15,7 +15,7 @@ class Gender(StrEnum):
|
|||||||
nonbinary = "n"
|
nonbinary = "n"
|
||||||
|
|
||||||
class HonoraryStatus(StrEnum):
|
class HonoraryStatus(StrEnum):
|
||||||
merit = ("merit",)
|
merit = "merit"
|
||||||
honorary = "honorary"
|
honorary = "honorary"
|
||||||
|
|
||||||
class OnlineAlmanacField(StrEnum):
|
class OnlineAlmanacField(StrEnum):
|
||||||
@ -1,13 +1,12 @@
|
|||||||
from typing import Literal
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from .non_members import FormerMemberModel
|
from .non_members import FormerMemberModel
|
||||||
from .enums import Membership, OnlineAlmanacField
|
from .enums import OnlineAlmanacField
|
||||||
from .submodels import Address, LoginInfo
|
from .submodels import Address, LoginInfo
|
||||||
|
|
||||||
class MemberModel(FormerMemberModel):
|
class MemberModel(FormerMemberModel):
|
||||||
# requires that Membership equals member
|
# requires that Membership equals member
|
||||||
membership: Literal[Membership.member] = Membership.member
|
# membership: Literal[Membership.member] = Membership.member
|
||||||
|
|
||||||
login: LoginInfo
|
login: LoginInfo
|
||||||
address: Address
|
address: Address
|
||||||
@ -1,8 +1,6 @@
|
|||||||
from typing import Literal
|
|
||||||
from pydantic import Field
|
|
||||||
|
|
||||||
from .person import PersonModel
|
from .person import PersonModel
|
||||||
from .enums import HonoraryStatus, Membership, Gender
|
from .enums import HonoraryStatus, Gender
|
||||||
|
|
||||||
class FormerMemberModel(PersonModel):
|
class FormerMemberModel(PersonModel):
|
||||||
title: str | None = None
|
title: str | None = None
|
||||||
@ -13,12 +11,13 @@ class FormerMemberModel(PersonModel):
|
|||||||
# company emails?
|
# company emails?
|
||||||
|
|
||||||
class AlumnModel(FormerMemberModel):
|
class AlumnModel(FormerMemberModel):
|
||||||
membership: Literal[Membership.alumn] = Membership.alumn
|
# membership: Literal[Membership.alumn] = Membership.alumn
|
||||||
|
|
||||||
graduated: bool = False
|
graduated: bool = False
|
||||||
receive_spam_mail: bool = True
|
receive_spam_mail: bool = True
|
||||||
|
|
||||||
class ExternalModel(PersonModel):
|
class ExternalModel(PersonModel):
|
||||||
membership: Literal[Membership.extern] = Membership.extern
|
# membership: Literal[Membership.extern] = Membership.extern
|
||||||
|
pass
|
||||||
# TODO... payment???
|
# TODO... payment???
|
||||||
# TODO require login info??
|
# TODO require login info??
|
||||||
@ -1,10 +1,13 @@
|
|||||||
from pydantic import BaseModel, model_validator, EmailStr
|
from pydantic import model_validator, EmailStr
|
||||||
from typing import Self
|
from typing import Self, Literal
|
||||||
|
|
||||||
|
from fiber_package.base import FiberBaseModel
|
||||||
from .enums import Membership, LanguageOption
|
from .enums import Membership, LanguageOption
|
||||||
from .submodels import Address
|
from .submodels import Address
|
||||||
|
|
||||||
class PersonModel(BaseModel):
|
class PersonModel(FiberBaseModel):
|
||||||
|
collection_name: Literal["entity"] = "entity"
|
||||||
|
|
||||||
membership: Membership
|
membership: Membership
|
||||||
first_name: str
|
first_name: str
|
||||||
tussenvoegsels: str | None = None # only in Dutch names
|
tussenvoegsels: str | None = None # only in Dutch names
|
||||||
7
src/fiber_package/entity/system.py
Normal file
7
src/fiber_package/entity/system.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
from .submodels import LoginInfo, LoginInfoResponse
|
||||||
|
|
||||||
|
from fiber_package.base import FiberBaseModel
|
||||||
|
|
||||||
|
class LoginModel(FiberBaseModel):
|
||||||
|
login: LoginInfo | LoginInfoResponse
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
from fiber_package.util import GroupList, EntityList
|
|
||||||
|
|
||||||
class RootGroupModel(BaseModel):
|
|
||||||
associations: GroupList
|
|
||||||
sys_admins: EntityList
|
|
||||||
@ -1,9 +1,11 @@
|
|||||||
from pydantic import BaseModel, model_validator
|
from pydantic import model_validator
|
||||||
from fiber_package.util import EntityList
|
from fiber_package.refs import EntityRefList, EntityRef
|
||||||
|
|
||||||
from datetime import date
|
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
|
Removing old members from last_active is done in fiber
|
||||||
'''
|
'''
|
||||||
|
|
||||||
class GuildModel(BaseModel):
|
class GuildModel(FiberBaseModel):
|
||||||
full_name_EN: str
|
collection_name: Literal["guild"] = "guild"
|
||||||
full_name_NL: str
|
|
||||||
description_EN: str
|
|
||||||
description_NL: str
|
|
||||||
|
|
||||||
members: EntityList
|
full_name_en: str
|
||||||
previous_members: EntityList
|
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]
|
last_active: dict[EntityRef, date]
|
||||||
|
|
||||||
@ -30,7 +34,9 @@ class GuildModel(BaseModel):
|
|||||||
def current_members_not_previous(self) -> Self:
|
def current_members_not_previous(self) -> Self:
|
||||||
for member in self.members:
|
for member in self.members:
|
||||||
if member in self.previous_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
|
return self
|
||||||
|
|
||||||
@model_validator(mode='after')
|
@model_validator(mode='after')
|
||||||
@ -39,4 +45,3 @@ class GuildModel(BaseModel):
|
|||||||
if master not in self.members:
|
if master not in self.members:
|
||||||
raise ValueError("All guild masters must be members!")
|
raise ValueError("All guild masters must be members!")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -1,23 +1,42 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pydantic import UUID7
|
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)
|
@dataclass(slots=True)
|
||||||
class SystemRef(Ref):
|
class Ref[C: str]:
|
||||||
uuid: UUID7
|
uuid: UUID7
|
||||||
collection: Literal["system"] = "system"
|
collection_name: C
|
||||||
|
|
||||||
class CommitteeRef(Ref):
|
@dataclass(slots=True)
|
||||||
uuid: UUID7
|
class RefList[C: str]:
|
||||||
collection: Literal["committee"] = "committee"
|
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)]
|
EntityRef = Ref[Literal["entity"]]
|
||||||
GroupList = Annotated[list[UUID4], Field(default_factory=list)]
|
EntityRefList = RefList[Literal["entity"]]
|
||||||
|
|
||||||
EntityRef = UUID4
|
CommitteeRef = Ref[Literal["committee"]]
|
||||||
GroupRef = UUID4
|
CommitteeRefList = RefList[Literal["committee"]]
|
||||||
|
|
||||||
|
CommitteeEditionRef = Ref[Literal["committee_edition"]]
|
||||||
|
CommitteeEditionRefList = RefList[Literal["committee_edition"]]
|
||||||
|
|
||||||
|
GuildRef = Ref[Literal["guild"]]
|
||||||
|
GuildRefList = RefList[Literal["guild"]]
|
||||||
|
|||||||
15
src/fiber_package/system/association.py
Normal file
15
src/fiber_package/system/association.py
Normal 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
|
||||||
6
src/fiber_package/system/root.py
Normal file
6
src/fiber_package/system/root.py
Normal 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
|
||||||
Loading…
x
Reference in New Issue
Block a user