mirror of
https://github.com/enpaul/keyosk.git
synced 2024-11-24 23:47:49 +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