Docstrings

This commit is contained in:
Jörn-Michael Miehe 2022-03-28 20:58:40 +00:00
parent 3d2abbc39b
commit 1b24861e48
6 changed files with 127 additions and 33 deletions

View file

@ -1,7 +1,11 @@
from .capability import Capability """
Package `db`: ORM and schemas for database content.
"""
from .connection import Connection from .connection import Connection
from .device import Device, DeviceBase, DeviceCreate from .device import Device, DeviceBase, DeviceCreate
from .user import User, UserBase, UserCreate, UserRead from .user import User, UserBase, UserCreate, UserRead
from .user_capability import Capability
__all__ = ["Capability", "Connection", "Device", "DeviceBase", "DeviceCreate", __all__ = ["Capability", "Connection", "Device", "DeviceBase", "DeviceCreate",
"User", "UserBase", "UserCreate", "UserRead"] "User", "UserBase", "UserCreate", "UserRead"]

View file

@ -1,9 +1,13 @@
"""
Database connection management
"""
from sqlmodel import Session, SQLModel, create_engine from sqlmodel import Session, SQLModel, create_engine
class Connection: class Connection:
""" """
Namespace for the database connection. Namespace for the database connection
""" """
engine = None engine = None

View file

@ -1,3 +1,7 @@
"""
Python representation of `device` table.
"""
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime
@ -13,16 +17,36 @@ if TYPE_CHECKING:
class DeviceBase(SQLModel): class DeviceBase(SQLModel):
"""
Common to all representations of devices
"""
name: str name: str
type: str type: str
expiry: datetime | None expiry: datetime | None
class DeviceCreate(DeviceBase): class DeviceCreate(DeviceBase):
"""
Representation of a newly created device
"""
owner_name: str | None
class DeviceRead(DeviceBase):
"""
Representation of a device read via the API
"""
owner_name: str | None owner_name: str | None
class Device(DeviceBase, table=True): class Device(DeviceBase, table=True):
"""
Representation of device table
"""
__table_args__ = (UniqueConstraint( __table_args__ = (UniqueConstraint(
"owner_name", "owner_name",
"name", "name",

View file

@ -1,3 +1,7 @@
"""
Python representation of `user` table.
"""
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any
@ -7,12 +11,16 @@ from sqlalchemy.exc import IntegrityError
from sqlmodel import Field, Relationship, SQLModel from sqlmodel import Field, Relationship, SQLModel
from ..config import Config from ..config import Config
from .capability import Capability, UserCapability
from .connection import Connection from .connection import Connection
from .device import Device from .device import Device
from .user_capability import Capability, UserCapability
class UserBase(SQLModel): class UserBase(SQLModel):
"""
Common to all representations of users
"""
name: str = Field(primary_key=True) name: str = Field(primary_key=True)
email: str | None = Field(default=None) email: str | None = Field(default=None)
@ -23,7 +31,50 @@ class UserBase(SQLModel):
organizational_unit: str | None = Field(default=None) organizational_unit: str | None = Field(default=None)
class UserCreate(UserBase):
"""
Representation of a newly created user
"""
password: str | None = Field(default=None)
password_clear: str | None = Field(default=None)
@root_validator
@classmethod
def hash_password(cls, values: dict[str, Any]) -> dict[str, Any]:
"""
Ensure the `password` value of this user gets set.
"""
if (values.get("password")) is not None:
# password is set
return values
if (password_clear := values.get("password_clear")) is None:
raise ValueError("No password to hash")
if (current_config := Config._) is None:
raise ValueError("Not configured")
values["password"] = current_config.crypto.crypt_context.hash(
password_clear)
return values
class UserRead(UserBase):
"""
Representation of a user read via the API
"""
pass
class User(UserBase, table=True): class User(UserBase, table=True):
"""
Representation of user table
"""
password: str password: str
capabilities: list[UserCapability] = Relationship( capabilities: list[UserCapability] = Relationship(
@ -109,46 +160,31 @@ class User(UserBase, table=True):
db.delete(self) db.delete(self)
db.commit() db.commit()
def can(self, capability: Capability) -> bool:
return capability in self.get_capabilities()
def get_capabilities(self) -> set[Capability]: def get_capabilities(self) -> set[Capability]:
"""
Return the capabilities of this user.
"""
return set( return set(
capability._ capability._
for capability in self.capabilities for capability in self.capabilities
) )
def can(self, capability: Capability) -> bool:
"""
Check if this user has a capability.
"""
return capability in self.get_capabilities()
def set_capabilities(self, capabilities: set[Capability]) -> None: def set_capabilities(self, capabilities: set[Capability]) -> None:
"""
Change the capabilities of this user.
"""
self.capabilities = [ self.capabilities = [
UserCapability( UserCapability(
user_name=self.name, user_name=self.name,
capability_name=capability.value, capability_name=capability.value,
) for capability in capabilities ) for capability in capabilities
] ]
class UserCreate(UserBase):
password: str | None = Field(default=None)
password_clear: str | None = Field(default=None)
@root_validator
@classmethod
def hash_password(cls, values: dict[str, Any]) -> dict[str, Any]:
if (values.get("password")) is not None:
# password is set
return values
if (password_clear := values.get("password_clear")) is None:
raise ValueError("No password to hash")
if (current_config := Config._) is None:
raise ValueError("Not configured")
values["password"] = current_config.crypto.crypt_context.hash(
password_clear)
return values
class UserRead(UserBase):
pass

View file

@ -1,3 +1,7 @@
"""
Python representation of `usercapability` table.
"""
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -8,6 +12,10 @@ if TYPE_CHECKING:
class Capability(Enum): class Capability(Enum):
"""
Allowed values for capabilities
"""
admin = "admin" admin = "admin"
login = "login" login = "login"
issue = "issue" issue = "issue"
@ -18,10 +26,18 @@ class Capability(Enum):
class UserCapabilityBase(SQLModel): class UserCapabilityBase(SQLModel):
"""
Common to all representations of capabilities
"""
capability_name: str = Field(primary_key=True) capability_name: str = Field(primary_key=True)
@property @property
def _(self) -> Capability: def _(self) -> Capability:
"""
Transform into a `Capability`.
"""
return Capability(self.capability_name) return Capability(self.capability_name)
def __repr__(self) -> str: def __repr__(self) -> str:
@ -29,6 +45,10 @@ class UserCapabilityBase(SQLModel):
class UserCapability(UserCapabilityBase, table=True): class UserCapability(UserCapabilityBase, table=True):
"""
Representation of usercapability table
"""
user_name: str = Field(primary_key=True, foreign_key="user.name") user_name: str = Field(primary_key=True, foreign_key="user.name")
user: "User" = Relationship( user: "User" = Relationship(

View file

@ -1,3 +1,9 @@
"""
Package `routers`: Each module contains the path operations for their prefixes.
This file: Main API router definition.
"""
from fastapi import APIRouter from fastapi import APIRouter
from . import admin, user from . import admin, user