mirror of
https://github.com/enpaul/keyosk.git
synced 2024-12-26 01:33:33 +00:00
Add initial database infrastructure
Add base model class Add initialize function and basic import structure Add datatypes module
This commit is contained in:
parent
ee99c1b9ab
commit
2b5eafa71a
95
keyosk/database/__init__.py
Normal file
95
keyosk/database/__init__.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""Database interface and tooling module
|
||||
|
||||
Keyosk uses the
|
||||
`Peewee Object Relational Model <http://docs.peewee-orm.com/en/latest/peewee/quickstart.html>`_
|
||||
Python library for its database interface. The database interface object is available as the
|
||||
``interface`` variable under the ``keyosk.database`` namespace. The individual models (a.k.a.
|
||||
database tables) are available in the same namespace.
|
||||
|
||||
The database connection can be accessed using the
|
||||
`Peewee atomic transaction context manager <http://docs.peewee-orm.com/en/latest/peewee/database.html#context-manager>`_.
|
||||
Similarly, the ORM models- which correspond to database tables- can be accessed in the same way:
|
||||
|
||||
::
|
||||
|
||||
from keyosk import database
|
||||
|
||||
with database.interface.atomic():
|
||||
database.Account.get(username == "atticusfinch")
|
||||
"""
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import Type
|
||||
|
||||
import peewee
|
||||
|
||||
from keyosk import config
|
||||
from keyosk import datatypes
|
||||
from keyosk.database._shared import INTERFACE as interface
|
||||
from keyosk.database._shared import KeyoskBaseModel
|
||||
from keyosk.database.account import Account
|
||||
from keyosk.database.domain import Domain
|
||||
from keyosk.database.mappings import AccountACL
|
||||
from keyosk.database.mappings import AccountAssignment
|
||||
from keyosk.database.mappings import DomainAccessList
|
||||
from keyosk.database.mappings import DomainAdmin
|
||||
from keyosk.database.mappings import DomainPermission
|
||||
|
||||
|
||||
MODELS: List[Type[KeyoskBaseModel]] = [
|
||||
Account,
|
||||
Domain,
|
||||
DomainAccessList,
|
||||
DomainPermission,
|
||||
DomainAdmin,
|
||||
AccountACL,
|
||||
AccountAssignment,
|
||||
]
|
||||
|
||||
|
||||
def initialize(conf: config.KeyoskConfig):
|
||||
"""Initialize the database interface
|
||||
|
||||
Defining the database as an
|
||||
`unconfigured proxy object <http://docs.peewee-orm.com/en/latest/peewee/database.html#setting-the-database-at-run-time>`_
|
||||
allows it to be configured at runtime based on the config values.
|
||||
|
||||
:param config: Populated configuration container object
|
||||
"""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if conf.storage.backend == datatypes.StorageBackend.SQLITE:
|
||||
logger.debug("Using SQLite database backend")
|
||||
pragmas = {
|
||||
**conf.storage.sqlite.pragmas,
|
||||
**{
|
||||
"journal_mode": "wal",
|
||||
"cache_size": -1 * 64000,
|
||||
"foreign_keys": 1,
|
||||
"ignore_check_constraints": 0,
|
||||
"synchronous": 0,
|
||||
},
|
||||
}
|
||||
for key, value in pragmas:
|
||||
logger.debug(f"Applying pragma '{key}' with value '{value}'")
|
||||
database = peewee.SqliteDatabase(conf.storage.sqlite.path, pragmas=pragmas)
|
||||
|
||||
elif conf.storage.backend == datatypes.StorageBackend.MARIA:
|
||||
logger.debug("Using MariaDB database backend")
|
||||
database = peewee.MySQLDatabase(
|
||||
conf.storage.maria.schema,
|
||||
host=conf.storage.maria.host,
|
||||
port=conf.storage.maria.port,
|
||||
user=conf.storage.maria.username,
|
||||
password=conf.storage.maria.password,
|
||||
charset="utf8mb4",
|
||||
)
|
||||
logger.debug(
|
||||
f"Configuring MariaDB: {conf.storage.maria.username}@{conf.storage.maria.host}:{conf.storage.maria.port} `{conf.storage.maria.schema}`"
|
||||
)
|
||||
|
||||
interface.initialize(database)
|
||||
|
||||
with interface.atomic():
|
||||
interface.create_tables(MODELS)
|
78
keyosk/database/_shared.py
Normal file
78
keyosk/database/_shared.py
Normal file
@ -0,0 +1,78 @@
|
||||
"""Internally shared database components
|
||||
|
||||
This submodule exists to avoid circular imports: architecturally there's no reason why
|
||||
this module's base model and the :func:`initialize` function cannot both go in
|
||||
``__init__``, or indeed why they can't both go here. However if both existed in the same
|
||||
module then every other submodule would need to both import :class:`KeyoskBaseModel`
|
||||
from that module, and be imported into that module for the :func:`initialize` function
|
||||
to work. This would lead to a circular import.
|
||||
|
||||
Thus the :func:`initialize` function and :class:`KeyoskBaseModel` class need to go in
|
||||
separate modules, and for somewhat arbitrary reasons the base model was put here and the
|
||||
init function kept in init.
|
||||
"""
|
||||
from typing import Any
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
import peewee
|
||||
|
||||
|
||||
INTERFACE = peewee.DatabaseProxy()
|
||||
|
||||
|
||||
class KeyoskBaseModel(peewee.Model):
|
||||
"""Base model for primary models to inherit from
|
||||
|
||||
* Attaches the ``uuid`` field to the model as the primary key
|
||||
* Attaches the model- and all child models- to the database proxy
|
||||
* Provides the structure for casting the model to a dictionary
|
||||
|
||||
.. warning:: This model is a stub and should not be created in the database or
|
||||
used for querying.
|
||||
"""
|
||||
|
||||
class Meta: # pylint: disable=missing-docstring,too-few-public-methods
|
||||
database = INTERFACE
|
||||
|
||||
uuid = peewee.UUIDField(null=False, unique=True, primary_key=True)
|
||||
|
||||
@staticmethod
|
||||
def dict_keys() -> List[str]:
|
||||
"""Return tuple of attribute names that should be included in the dict form of the model
|
||||
Inteneded to be used in a dictionary comprehension; see the :meth:`__iter__` method for
|
||||
usage example.
|
||||
"""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def foreign_ref() -> List[str]:
|
||||
"""Return tuple of attribute names that point to foreign key references on the model
|
||||
Intended for usage when recursively converting models into dictionaries ahead of
|
||||
serialization; see the :meth:`__iter__` method for usage example.
|
||||
|
||||
.. warning:: Foreign keys should only be included here when their attribute appears in the
|
||||
tuple returned from :meth:`dict_keys`
|
||||
"""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def foreign_backref() -> List[str]:
|
||||
"""Return tuple of attribute names that point to foreign backreferences on the model
|
||||
Inteneded for usage when recursively converting models into dictionaries ahead of
|
||||
serialization; see the :meth:`__iter__` method for usage example.
|
||||
|
||||
.. warning:: Foreign keys should only be included here when their attribute appears in the
|
||||
tuple returned from :meth:`dict_keys`
|
||||
"""
|
||||
return []
|
||||
|
||||
def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
|
||||
for key in self.dict_keys():
|
||||
if key in self.foreign_ref():
|
||||
yield key, dict(getattr(self, key))
|
||||
elif key in self.foreign_backref():
|
||||
yield key, [dict(item) for item in getattr(self, key)]
|
||||
else:
|
||||
yield key, getattr(self, key)
|
17
keyosk/datatypes.py
Normal file
17
keyosk/datatypes.py
Normal file
@ -0,0 +1,17 @@
|
||||
import enum
|
||||
from typing import Dict
|
||||
from typing import Union
|
||||
|
||||
|
||||
Extras = Dict[str, Union[int, float, bool, str, None]]
|
||||
|
||||
|
||||
class TokenUsage(enum.Enum):
|
||||
REFRESH = enum.auto()
|
||||
ACCESS = enum.auto()
|
||||
|
||||
|
||||
@enum.unique
|
||||
class StorageBackend(enum.Enum):
|
||||
SQLITE = "sqlite"
|
||||
MARIA = "maria"
|
Loading…
Reference in New Issue
Block a user