Source code for loglan_core.base

"""
Initial common functions for LOD Model Classes
"""

from datetime import datetime

from sqlalchemy import String, inspect, func, select
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.orm import Session, registry as rg

from loglan_core.service.annotated_types import (
    str_008,
    str_016,
    str_032,
    str_064,
    str_128,
    str_255,
)


[docs] class BaseModel(AsyncAttrs, DeclarativeBase): """ BaseModel is a subclass of DeclarativeBase. It serves as the parent class for other database models, providing common attributes and methods to its child classes. It doesn't contain any class-specific attributes/methods. """ registry = rg( type_annotation_map={ str_008: String(8), str_016: String(16), str_032: String(32), str_064: String(64), str_128: String(128), str_255: String(255), } ) """ This code creates a registry that maps custom type annotations to SQLAlchemy String types with specified lengths. The keys are custom type annotation names and the values are SQLAlchemy String types with the specified lengths. """ __abstract__ = True """ A class attribute that indicates that this class is an abstract base class. When set to True, this class won't be mapped to any database table. Instead, it serves as a base for other classes which will be mapped to tables. """ id: Mapped[int] = mapped_column(primary_key=True) """ A class attribute mapped to a column in the database table. It serves as the primary key for the table. :type: int """ created: Mapped[datetime] = mapped_column(default=datetime.now(), nullable=False) """ A class attribute mapped to a column in the database table. It represents the timestamp when a row is created. The default value is the current timestamp, and it can't be null. :type: datetime """ updated: Mapped[datetime | None] = mapped_column( onupdate=func.now() # pylint: disable=E1102 ) """ A class attribute mapped to a column in the database table. It represents the timestamp when a row is last updated. Whenever the row is updated, this timestamp is automatically set to the current time. It can be ``null`` if the row has never been updated. :type: datetime """
[docs] def __repr__(self): """ Special method that returns a string representation of the object. It forms the string by joining key-value pairs of the object's attributes, excluding keys that start with "_" and keys that are "created" or "updated". The key-value pairs are sorted before joining. Returns: str: A string representation of the object in the format: "ClassName(key1=value1, key2=value2, ...)". """ obj_str = ", ".join( sorted( [ f"{k}={v!r}" for k, v in self.__dict__.items() if self._filter_add_to_repr(k, v) ] ) ) return f"{self.__class__.__name__}({obj_str})"
[docs] @classmethod def _filter_add_to_repr(cls, k, v): """ Static method that filters out keys that start with "_" and keys that are "created" or "updated" and keys without values from the object's attributes. The method is used to generate a string representation of the object. It is used internally by the __repr__. """ return ( not k.startswith("_") and k not in ["created", "updated", *cls.relationships()] and v )
[docs] @classmethod def get_by_id(cls, session: Session, cid: int): """ Class method that retrieves an instance of the class from the database using the provided session and id. Parameters: session (Session): The session to use for the database query. cid (int): The id of the instance to retrieve. Returns: Object of class type or None: The instance of the class with the given id, or None if no such instance exists. """ return session.get(cls, cid)
[docs] @classmethod def get_all(cls, session: Session): """ Class method that retrieves all instances of the class from the database using the provided session. Parameters: session (Session): The session to use for the database query. Returns: list: A list of all instances of the class. """ return session.scalars(select(cls)).all()
[docs] def export(self): """ Class method that exports the object's attributes into a dictionary. It filters out keys that start with "_" and keys that are "created" or "updated", then sorts the remaining keys. Returns: dict: A dictionary with sorted keys and corresponding values of the object's attributes. """ return { k: v for k, v in sorted(self.__dict__.items()) if not str(k).startswith("_") and k not in ["created", "updated", *self.relationships()] }
[docs] @classmethod def attributes_all(cls) -> set[str]: """ Class method that computes the all attribute keys from the class mapper and returns their names as a set. It doesn't require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of all attribute keys. """ return set(cls.__mapper__.attrs.keys()) | cls.properties()
[docs] @classmethod def attributes_basic(cls) -> set[str]: """ Class method that computes the set of basic attributes for the class. It subtracts any relationships from all attributes. It doesn’t require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of basic attributes. """ return set(cls.attributes_all() - cls.relationships() - cls.properties())
[docs] @classmethod def attributes_extended(cls) -> set[str]: """ Class method that computes the extended attributes of the class. It does this by subtracting foreign keys from all attributes. It doesn’t require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of the extended attributes. """ return set(cls.attributes_all() - cls.foreign_keys())
[docs] @classmethod def relationships(cls) -> set[str]: """ Class method that computes the relationship names from the class mapper and returns them as a set. It doesn't require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of the relationships. """ return set(cls.__mapper__.relationships.keys())
[docs] @classmethod def foreign_keys(cls) -> set[str]: """ Class method that computes the names of foreign keys of the class. It does this by subtracting relationship keys and non-foreign keys from all attributes. It doesn’t require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of the foreign keys. """ return set( cls.attributes_all() - cls.relationships() - cls.non_foreign_keys() - cls.properties() )
[docs] @classmethod def non_foreign_keys(cls) -> set[str]: """ Class method that computes the non-foreign keys of the class. It does this by inspecting the class columns and selecting those without foreign keys. It doesn’t require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of the non-foreign keys. """ inspector = inspect(cls) columns = inspector.columns non_foreign_keys = { column.name for column in columns if not column.foreign_keys } return non_foreign_keys
[docs] @classmethod def hybrid_properties(cls) -> set[str]: """ Class method that computes the hybrid properties of the class. It doesn’t require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of the hybrid properties. """ inspector = inspect(cls).all_orm_descriptors return {i.__name__ for i in inspector if isinstance(i, hybrid_property)}
[docs] @classmethod def properties(cls): """ Class method that computes the properties of the class. It doesn’t require any parameters as it operates on the class itself. Returns: set[str]: A set of strings with names of the properties. """ return { key for key, value in cls.__dict__.items() if isinstance(value, property) }