Module pyracmon.model_graph

This module provides graph specifications to deal with model types.

Most of them are not used directly except for ConfigurableSpec which is an attribute of PyracmonConfiguration .

Expand source code
"""
This module provides graph specifications to deal with model types.

Most of them are not used directly except for `ConfigurableSpec` which is an attribute of `PyracmonConfiguration` .
"""
from typing import Optional, Any, cast
import inspect
from .model import Model, Meta
from .graph.spec import GraphSpec
from .graph.typing import DynamicType, Shrink, document_type
from .graph.serialize import T, Serializer
from .graph.typing import TypedDict, issubgeneric


class GraphEntityMixin(Meta):
    """
    Mixin class for model types which enables identity calculation and nullity check.
    """
    @classmethod
    def identity(cls, model: Model) -> Optional[Any]:
        """
        Returns primary key values as the identity of a model.

        Args:
            model: A model object.
        Returns:
            Primary key value(s). `None` if the model type does not have primary key(s).
        """
        pks = [c.name for c in cls.columns if c.pk]
        if len(pks) > 0 and all([hasattr(model, n) and getattr(model, n) is not None for n in pks]):
            return tuple(map(lambda n: getattr(model, n), pks))
        else:
            return None

    @classmethod
    def is_null(cls, model: Model) -> bool:
        """
        Checks the model is considered to be null.

        Args:
            model: A model object.
        Returns:
            Whether all column values are `None`.
        """
        return all([getattr(model, c.name, None) is None for c in cls.columns])


class ModelSchema(DynamicType[T]):
    """
    Schema of model type `T`.
    """
    @classmethod
    def fix(cls, bound, arg):
        class Schema(TypedDict):
            pass
        bound = cast(type[Meta], bound)
        setattr(Schema, '__annotations__', {c.name:document_type(c.ptype, c.comment) for c in bound.columns})
        return Schema


class ExcludeFK(Shrink[T]):
    """
    Schema converter which excludes foreign key columns from schema of model type `T` .
    """
    @classmethod
    def select(cls, bound, arg):
        arg = cast(type[Meta], arg)
        return {c.name for c in arg.columns if c.fk}, None


class ConfigurableSpec(GraphSpec):
    """
    Extension of `GraphSpec` prepared to integrate model types into graph specification.

    This class exposes additional configurable attributes which controls the graph operation for model types.
    In global configuration, `graph_spec` attribute is an instance of this class,
    thus changes on it changes graph operations on model types.

    .. warning::
        The implementation of this class is not stable, don't depends on it.
    """
    @classmethod
    def create(cls):
        spec = cls()

        spec.add_identifier(GraphEntityMixin, lambda m: type(m).identity(m))
        spec.add_entity_filter(GraphEntityMixin, lambda m: m and not type(m).is_null(m))

        def serialize(cxt) -> ModelSchema[T]:
            return cast(ModelSchema, {c.name:v for c, v in cxt.value})
        spec.add_serializer(GraphEntityMixin, serialize)

        return spec

    def __init__(self, *args):
        super(ConfigurableSpec, self).__init__(*args)

        #: A flag which determines whether including foreign key columns in the result of graph serialization.
        self.include_fk = False

    def __deepcopy__(self, memo):
        spec = ConfigurableSpec(
            self.identifiers.copy(),
            self.entity_filters.copy(),
            self.serializers.copy(),
        )
        spec.include_fk = self.include_fk
        return spec

    def _model_serializer(self, bases: list[Serializer]) -> list[Serializer]:
        """
        Generate configured serializer for model type.

        Args:
            bases: Serialization functions.
        Returns:
        """
        if not self.include_fk:
            def serialize(cxt) -> ExcludeFK[T]:
                fk = {c.name for c, _ in cxt.value if c.fk}
                values = cxt.serialize()
                return cast(ExcludeFK, {c:v for c, v in values.items() if not c in fk})

            pos = next(filter(lambda ib: issubgeneric(inspect.signature(ib[1]).return_annotation, ModelSchema), enumerate(bases)), None)

            return bases[0:pos[0]+1] + [serialize] + bases[pos[0]+1:] if pos else bases
        else:
            return bases

    def find_serializers(self, t) -> list[Serializer]:
        bases = super(ConfigurableSpec, self).find_serializers(t)

        if issubclass(t, GraphEntityMixin):
            return self._model_serializer(bases)
        else:
            return bases

Classes

class ConfigurableSpec (*args)

Extension of GraphSpec prepared to integrate model types into graph specification.

This class exposes additional configurable attributes which controls the graph operation for model types. In global configuration, graph_spec attribute is an instance of this class, thus changes on it changes graph operations on model types.

Warning

The implementation of this class is not stable, don't depends on it.

Expand source code
class ConfigurableSpec(GraphSpec):
    """
    Extension of `GraphSpec` prepared to integrate model types into graph specification.

    This class exposes additional configurable attributes which controls the graph operation for model types.
    In global configuration, `graph_spec` attribute is an instance of this class,
    thus changes on it changes graph operations on model types.

    .. warning::
        The implementation of this class is not stable, don't depends on it.
    """
    @classmethod
    def create(cls):
        spec = cls()

        spec.add_identifier(GraphEntityMixin, lambda m: type(m).identity(m))
        spec.add_entity_filter(GraphEntityMixin, lambda m: m and not type(m).is_null(m))

        def serialize(cxt) -> ModelSchema[T]:
            return cast(ModelSchema, {c.name:v for c, v in cxt.value})
        spec.add_serializer(GraphEntityMixin, serialize)

        return spec

    def __init__(self, *args):
        super(ConfigurableSpec, self).__init__(*args)

        #: A flag which determines whether including foreign key columns in the result of graph serialization.
        self.include_fk = False

    def __deepcopy__(self, memo):
        spec = ConfigurableSpec(
            self.identifiers.copy(),
            self.entity_filters.copy(),
            self.serializers.copy(),
        )
        spec.include_fk = self.include_fk
        return spec

    def _model_serializer(self, bases: list[Serializer]) -> list[Serializer]:
        """
        Generate configured serializer for model type.

        Args:
            bases: Serialization functions.
        Returns:
        """
        if not self.include_fk:
            def serialize(cxt) -> ExcludeFK[T]:
                fk = {c.name for c, _ in cxt.value if c.fk}
                values = cxt.serialize()
                return cast(ExcludeFK, {c:v for c, v in values.items() if not c in fk})

            pos = next(filter(lambda ib: issubgeneric(inspect.signature(ib[1]).return_annotation, ModelSchema), enumerate(bases)), None)

            return bases[0:pos[0]+1] + [serialize] + bases[pos[0]+1:] if pos else bases
        else:
            return bases

    def find_serializers(self, t) -> list[Serializer]:
        bases = super(ConfigurableSpec, self).find_serializers(t)

        if issubclass(t, GraphEntityMixin):
            return self._model_serializer(bases)
        else:
            return bases

Ancestors

Static methods

def create()
Expand source code
@classmethod
def create(cls):
    spec = cls()

    spec.add_identifier(GraphEntityMixin, lambda m: type(m).identity(m))
    spec.add_entity_filter(GraphEntityMixin, lambda m: m and not type(m).is_null(m))

    def serialize(cxt) -> ModelSchema[T]:
        return cast(ModelSchema, {c.name:v for c, v in cxt.value})
    spec.add_serializer(GraphEntityMixin, serialize)

    return spec

Instance variables

var include_fk

A flag which determines whether including foreign key columns in the result of graph serialization.

Inherited members

class ExcludeFK

Schema converter which excludes foreign key columns from schema of model type T .

Expand source code
class ExcludeFK(Shrink[T]):
    """
    Schema converter which excludes foreign key columns from schema of model type `T` .
    """
    @classmethod
    def select(cls, bound, arg):
        arg = cast(type[Meta], arg)
        return {c.name for c in arg.columns if c.fk}, None

Ancestors

Inherited members

class GraphEntityMixin

Mixin class for model types which enables identity calculation and nullity check.

Expand source code
class GraphEntityMixin(Meta):
    """
    Mixin class for model types which enables identity calculation and nullity check.
    """
    @classmethod
    def identity(cls, model: Model) -> Optional[Any]:
        """
        Returns primary key values as the identity of a model.

        Args:
            model: A model object.
        Returns:
            Primary key value(s). `None` if the model type does not have primary key(s).
        """
        pks = [c.name for c in cls.columns if c.pk]
        if len(pks) > 0 and all([hasattr(model, n) and getattr(model, n) is not None for n in pks]):
            return tuple(map(lambda n: getattr(model, n), pks))
        else:
            return None

    @classmethod
    def is_null(cls, model: Model) -> bool:
        """
        Checks the model is considered to be null.

        Args:
            model: A model object.
        Returns:
            Whether all column values are `None`.
        """
        return all([getattr(model, c.name, None) is None for c in cls.columns])

Ancestors

Static methods

def identity(model: Model) ‑> Optional[Any]

Returns primary key values as the identity of a model.

Args

model
A model object.

Returns

Primary key value(s). None if the model type does not have primary key(s).

Expand source code
@classmethod
def identity(cls, model: Model) -> Optional[Any]:
    """
    Returns primary key values as the identity of a model.

    Args:
        model: A model object.
    Returns:
        Primary key value(s). `None` if the model type does not have primary key(s).
    """
    pks = [c.name for c in cls.columns if c.pk]
    if len(pks) > 0 and all([hasattr(model, n) and getattr(model, n) is not None for n in pks]):
        return tuple(map(lambda n: getattr(model, n), pks))
    else:
        return None
def is_null(model: Model) ‑> bool

Checks the model is considered to be null.

Args

model
A model object.

Returns

Whether all column values are None.

Expand source code
@classmethod
def is_null(cls, model: Model) -> bool:
    """
    Checks the model is considered to be null.

    Args:
        model: A model object.
    Returns:
        Whether all column values are `None`.
    """
    return all([getattr(model, c.name, None) is None for c in cls.columns])
class ModelSchema

Schema of model type T.

Expand source code
class ModelSchema(DynamicType[T]):
    """
    Schema of model type `T`.
    """
    @classmethod
    def fix(cls, bound, arg):
        class Schema(TypedDict):
            pass
        bound = cast(type[Meta], bound)
        setattr(Schema, '__annotations__', {c.name:document_type(c.ptype, c.comment) for c in bound.columns})
        return Schema

Ancestors

Inherited members