tiago/db_structure #2
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@ build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
.mypy_cache
|
||||
|
||||
# Virtual environments
|
||||
.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 = [
|
||||
"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"
|
||||
|
||||
class HonoraryStatus(StrEnum):
|
||||
merit = ("merit",)
|
||||
merit = "merit"
|
||||
honorary = "honorary"
|
||||
|
||||
class OnlineAlmanacField(StrEnum):
|
||||
@ -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
|
||||
@ -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??
|
||||
@ -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
|
||||
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 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
|
||||
|
||||
@ -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"]]
|
||||
|
||||
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