Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
model.py331 linesDownload Raw Back to flask_sqlalchemy
1from __future__ import annotations2 3import re4import typing as t5 6import sqlalchemy as sa7import sqlalchemy.orm as sa_orm8 9from .query import Query10 11if t.TYPE_CHECKING:12    from .extension import SQLAlchemy13 14 15class _QueryProperty:16    """A class property that creates a query object for a model.17 18    :meta private:19    """20 21    def __get__(self, obj: Model | None, cls: type[Model]) -> Query:22        return cls.query_class(23            cls, session=cls.__fsa__.session()  # type: ignore[arg-type]24        )25 26 27class Model:28    """The base class of the :attr:`.SQLAlchemy.Model` declarative model class.29 30    To define models, subclass :attr:`db.Model <.SQLAlchemy.Model>`, not this. To31    customize ``db.Model``, subclass this and pass it as ``model_class`` to32    :class:`.SQLAlchemy`. To customize ``db.Model`` at the metaclass level, pass an33    already created declarative model class as ``model_class``.34    """35 36    __fsa__: t.ClassVar[SQLAlchemy]37    """Internal reference to the extension object.38 39    :meta private:40    """41 42    query_class: t.ClassVar[type[Query]] = Query43    """Query class used by :attr:`query`. Defaults to :attr:`.SQLAlchemy.Query`, which44    defaults to :class:`.Query`.45    """46 47    query: t.ClassVar[Query] = _QueryProperty()  # type: ignore[assignment]48    """A SQLAlchemy query for a model. Equivalent to ``db.session.query(Model)``. Can be49    customized per-model by overriding :attr:`query_class`.50 51    .. warning::52        The query interface is considered legacy in SQLAlchemy. Prefer using53        ``session.execute(select())`` instead.54    """55 56    def __repr__(self) -> str:57        state = sa.inspect(self)58        assert state is not None59 60        if state.transient:61            pk = f"(transient {id(self)})"62        elif state.pending:63            pk = f"(pending {id(self)})"64        else:65            pk = ", ".join(map(str, state.identity))66 67        return f"<{type(self).__name__} {pk}>"68 69 70class BindMetaMixin(type):71    """Metaclass mixin that sets a model's ``metadata`` based on its ``__bind_key__``.72 73    If the model sets ``metadata`` or ``__table__`` directly, ``__bind_key__`` is74    ignored. If the ``metadata`` is the same as the parent model, it will not be set75    directly on the child model.76    """77 78    __fsa__: SQLAlchemy79    metadata: sa.MetaData80 81    def __init__(82        cls, name: str, bases: tuple[type, ...], d: dict[str, t.Any], **kwargs: t.Any83    ) -> None:84        if not ("metadata" in cls.__dict__ or "__table__" in cls.__dict__):85            bind_key = getattr(cls, "__bind_key__", None)86            parent_metadata = getattr(cls, "metadata", None)87            metadata = cls.__fsa__._make_metadata(bind_key)88 89            if metadata is not parent_metadata:90                cls.metadata = metadata91 92        super().__init__(name, bases, d, **kwargs)93 94 95class BindMixin:96    """DeclarativeBase mixin to set a model's ``metadata`` based on ``__bind_key__``.97 98    If no ``__bind_key__`` is specified, the model will use the default metadata99    provided by ``DeclarativeBase`` or ``DeclarativeBaseNoMeta``.100    If the model doesn't set ``metadata`` or ``__table__`` directly101    and does set ``__bind_key__``, the model will use the metadata102    for the specified bind key.103    If the ``metadata`` is the same as the parent model, it will not be set104    directly on the child model.105 106    .. versionchanged:: 3.1.0107    """108 109    __fsa__: SQLAlchemy110    metadata: sa.MetaData111 112    @classmethod113    def __init_subclass__(cls: t.Type[BindMixin], **kwargs: t.Dict[str, t.Any]) -> None:114        if not ("metadata" in cls.__dict__ or "__table__" in cls.__dict__) and hasattr(115            cls, "__bind_key__"116        ):117            bind_key = getattr(cls, "__bind_key__", None)118            parent_metadata = getattr(cls, "metadata", None)119            metadata = cls.__fsa__._make_metadata(bind_key)120 121            if metadata is not parent_metadata:122                cls.metadata = metadata123 124        super().__init_subclass__(**kwargs)125 126 127class NameMetaMixin(type):128    """Metaclass mixin that sets a model's ``__tablename__`` by converting the129    ``CamelCase`` class name to ``snake_case``. A name is set for non-abstract models130    that do not otherwise define ``__tablename__``. If a model does not define a primary131    key, it will not generate a name or ``__table__``, for single-table inheritance.132    """133 134    metadata: sa.MetaData135    __tablename__: str136    __table__: sa.Table137 138    def __init__(139        cls, name: str, bases: tuple[type, ...], d: dict[str, t.Any], **kwargs: t.Any140    ) -> None:141        if should_set_tablename(cls):142            cls.__tablename__ = camel_to_snake_case(cls.__name__)143 144        super().__init__(name, bases, d, **kwargs)145 146        # __table_cls__ has run. If no table was created, use the parent table.147        if (148            "__tablename__" not in cls.__dict__149            and "__table__" in cls.__dict__150            and cls.__dict__["__table__"] is None151        ):152            del cls.__table__153 154    def __table_cls__(cls, *args: t.Any, **kwargs: t.Any) -> sa.Table | None:155        """This is called by SQLAlchemy during mapper setup. It determines the final156        table object that the model will use.157 158        If no primary key is found, that indicates single-table inheritance, so no table159        will be created and ``__tablename__`` will be unset.160        """161        schema = kwargs.get("schema")162 163        if schema is None:164            key = args[0]165        else:166            key = f"{schema}.{args[0]}"167 168        # Check if a table with this name already exists. Allows reflected tables to be169        # applied to models by name.170        if key in cls.metadata.tables:171            return sa.Table(*args, **kwargs)172 173        # If a primary key is found, create a table for joined-table inheritance.174        for arg in args:175            if (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(176                arg, sa.PrimaryKeyConstraint177            ):178                return sa.Table(*args, **kwargs)179 180        # If no base classes define a table, return one that's missing a primary key181        # so SQLAlchemy shows the correct error.182        for base in cls.__mro__[1:-1]:183            if "__table__" in base.__dict__:184                break185        else:186            return sa.Table(*args, **kwargs)187 188        # Single-table inheritance, use the parent table name. __init__ will unset189        # __table__ based on this.190        if "__tablename__" in cls.__dict__:191            del cls.__tablename__192 193        return None194 195 196class NameMixin:197    """DeclarativeBase mixin that sets a model's ``__tablename__`` by converting the198    ``CamelCase`` class name to ``snake_case``. A name is set for non-abstract models199    that do not otherwise define ``__tablename__``. If a model does not define a primary200    key, it will not generate a name or ``__table__``, for single-table inheritance.201 202    .. versionchanged:: 3.1.0203    """204 205    metadata: sa.MetaData206    __tablename__: str207    __table__: sa.Table208 209    @classmethod210    def __init_subclass__(cls: t.Type[NameMixin], **kwargs: t.Dict[str, t.Any]) -> None:211        if should_set_tablename(cls):212            cls.__tablename__ = camel_to_snake_case(cls.__name__)213 214        super().__init_subclass__(**kwargs)215 216        # __table_cls__ has run. If no table was created, use the parent table.217        if (218            "__tablename__" not in cls.__dict__219            and "__table__" in cls.__dict__220            and cls.__dict__["__table__"] is None221        ):222            del cls.__table__223 224    @classmethod225    def __table_cls__(cls, *args: t.Any, **kwargs: t.Any) -> sa.Table | None:226        """This is called by SQLAlchemy during mapper setup. It determines the final227        table object that the model will use.228 229        If no primary key is found, that indicates single-table inheritance, so no table230        will be created and ``__tablename__`` will be unset.231        """232        schema = kwargs.get("schema")233 234        if schema is None:235            key = args[0]236        else:237            key = f"{schema}.{args[0]}"238 239        # Check if a table with this name already exists. Allows reflected tables to be240        # applied to models by name.241        if key in cls.metadata.tables:242            return sa.Table(*args, **kwargs)243 244        # If a primary key is found, create a table for joined-table inheritance.245        for arg in args:246            if (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(247                arg, sa.PrimaryKeyConstraint248            ):249                return sa.Table(*args, **kwargs)250 251        # If no base classes define a table, return one that's missing a primary key252        # so SQLAlchemy shows the correct error.253        for base in cls.__mro__[1:-1]:254            if "__table__" in base.__dict__:255                break256        else:257            return sa.Table(*args, **kwargs)258 259        # Single-table inheritance, use the parent table name. __init__ will unset260        # __table__ based on this.261        if "__tablename__" in cls.__dict__:262            del cls.__tablename__263 264        return None265 266 267def should_set_tablename(cls: type) -> bool:268    """Determine whether ``__tablename__`` should be generated for a model.269 270    -   If no class in the MRO sets a name, one should be generated.271    -   If a declared attr is found, it should be used instead.272    -   If a name is found, it should be used if the class is a mixin, otherwise one273        should be generated.274    -   Abstract models should not have one generated.275 276    Later, ``__table_cls__`` will determine if the model looks like single or277    joined-table inheritance. If no primary key is found, the name will be unset.278    """279    if (280        cls.__dict__.get("__abstract__", False)281        or (282            not issubclass(cls, (sa_orm.DeclarativeBase, sa_orm.DeclarativeBaseNoMeta))283            and not any(isinstance(b, sa_orm.DeclarativeMeta) for b in cls.__mro__[1:])284        )285        or any(286            (b is sa_orm.DeclarativeBase or b is sa_orm.DeclarativeBaseNoMeta)287            for b in cls.__bases__288        )289    ):290        return False291 292    for base in cls.__mro__:293        if "__tablename__" not in base.__dict__:294            continue295 296        if isinstance(base.__dict__["__tablename__"], sa_orm.declared_attr):297            return False298 299        return not (300            base is cls301            or base.__dict__.get("__abstract__", False)302            or not (303                # SQLAlchemy 1.x304                isinstance(base, sa_orm.DeclarativeMeta)305                # 2.x: DeclarativeBas uses this as metaclass306                or isinstance(base, sa_orm.decl_api.DeclarativeAttributeIntercept)307                # 2.x: DeclarativeBaseNoMeta doesn't use a metaclass308                or issubclass(base, sa_orm.DeclarativeBaseNoMeta)309            )310        )311 312    return True313 314 315def camel_to_snake_case(name: str) -> str:316    """Convert a ``CamelCase`` name to ``snake_case``."""317    name = re.sub(r"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))", r"_\1", name)318    return name.lower().lstrip("_")319 320 321class DefaultMeta(BindMetaMixin, NameMetaMixin, sa_orm.DeclarativeMeta):322    """SQLAlchemy declarative metaclass that provides ``__bind_key__`` and323    ``__tablename__`` support.324    """325 326 327class DefaultMetaNoName(BindMetaMixin, sa_orm.DeclarativeMeta):328    """SQLAlchemy declarative metaclass that provides ``__bind_key__`` and329    ``__tablename__`` support.330    """331 
codekingpro/portable-devtools · Team Ai