Docstrings
This commit is contained in:
parent
3d2abbc39b
commit
1b24861e48
6 changed files with 127 additions and 33 deletions
|
@ -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"]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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(
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue