lol
This commit is contained in:
@@ -4,43 +4,23 @@ import typing as t
|
||||
|
||||
from .extension import SQLAlchemy
|
||||
|
||||
__version__ = "3.0.5"
|
||||
|
||||
__all__ = [
|
||||
"SQLAlchemy",
|
||||
]
|
||||
|
||||
_deprecated_map = {
|
||||
"Model": ".model.Model",
|
||||
"DefaultMeta": ".model.DefaultMeta",
|
||||
"Pagination": ".pagination.Pagination",
|
||||
"BaseQuery": ".query.Query",
|
||||
"get_debug_queries": ".record_queries.get_recorded_queries",
|
||||
"SignallingSession": ".session.Session",
|
||||
"before_models_committed": ".track_modifications.before_models_committed",
|
||||
"models_committed": ".track_modifications.models_committed",
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> t.Any:
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
if name in _deprecated_map:
|
||||
path = _deprecated_map[name]
|
||||
import_path, _, new_name = path.rpartition(".")
|
||||
action = "moved and renamed"
|
||||
|
||||
if new_name == name:
|
||||
action = "moved"
|
||||
if name == "__version__":
|
||||
import importlib.metadata
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
f"'{name}' has been {action} to '{path[1:]}'. The top-level import is"
|
||||
" deprecated and will be removed in Flask-SQLAlchemy 3.1.",
|
||||
"The '__version__' attribute is deprecated and will be removed in"
|
||||
" Flask-SQLAlchemy 3.2. Use feature detection or"
|
||||
" 'importlib.metadata.version(\"flask-sqlalchemy\")' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
mod = importlib.import_module(import_path, __name__)
|
||||
return getattr(mod, new_name)
|
||||
return importlib.metadata.version("flask-sqlalchemy")
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import types
|
||||
import typing as t
|
||||
import warnings
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
import sqlalchemy as sa
|
||||
@@ -14,8 +16,11 @@ from flask import Flask
|
||||
from flask import has_app_context
|
||||
|
||||
from .model import _QueryProperty
|
||||
from .model import BindMixin
|
||||
from .model import DefaultMeta
|
||||
from .model import DefaultMetaNoName
|
||||
from .model import Model
|
||||
from .model import NameMixin
|
||||
from .pagination import Pagination
|
||||
from .pagination import SelectPagination
|
||||
from .query import Query
|
||||
@@ -26,6 +31,33 @@ from .table import _Table
|
||||
_O = t.TypeVar("_O", bound=object) # Based on sqlalchemy.orm._typing.py
|
||||
|
||||
|
||||
# Type accepted for model_class argument
|
||||
_FSA_MCT = t.TypeVar(
|
||||
"_FSA_MCT",
|
||||
bound=t.Union[
|
||||
t.Type[Model],
|
||||
sa_orm.DeclarativeMeta,
|
||||
t.Type[sa_orm.DeclarativeBase],
|
||||
t.Type[sa_orm.DeclarativeBaseNoMeta],
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Type returned by make_declarative_base
|
||||
class _FSAModel(Model):
|
||||
metadata: sa.MetaData
|
||||
|
||||
|
||||
def _get_2x_declarative_bases(
|
||||
model_class: _FSA_MCT,
|
||||
) -> list[t.Type[t.Union[sa_orm.DeclarativeBase, sa_orm.DeclarativeBaseNoMeta]]]:
|
||||
return [
|
||||
b
|
||||
for b in model_class.__bases__
|
||||
if issubclass(b, (sa_orm.DeclarativeBase, sa_orm.DeclarativeBaseNoMeta))
|
||||
]
|
||||
|
||||
|
||||
class SQLAlchemy:
|
||||
"""Integrates SQLAlchemy with Flask. This handles setting up one or more engines,
|
||||
associating tables and models with specific engines, and cleaning up connections and
|
||||
@@ -66,6 +98,18 @@ class SQLAlchemy:
|
||||
:param add_models_to_shell: Add the ``db`` instance and all model classes to
|
||||
``flask shell``.
|
||||
|
||||
.. versionchanged:: 3.1.0
|
||||
The ``metadata`` parameter can still be used with SQLAlchemy 1.x classes,
|
||||
but is ignored when using SQLAlchemy 2.x style of declarative classes.
|
||||
Instead, specify metadata on your Base class.
|
||||
|
||||
.. versionchanged:: 3.1.0
|
||||
Added the ``disable_autonaming`` parameter.
|
||||
|
||||
.. versionchanged:: 3.1.0
|
||||
Changed ``model_class`` parameter to accepta SQLAlchemy 2.x
|
||||
declarative base subclass.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
An active Flask application context is always required to access ``session`` and
|
||||
``engine``.
|
||||
@@ -100,11 +144,6 @@ class SQLAlchemy:
|
||||
.. versionchanged:: 3.0
|
||||
Removed the ``use_native_unicode`` parameter and config.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``COMMIT_ON_TEARDOWN`` configuration is deprecated and will
|
||||
be removed in Flask-SQLAlchemy 3.1. Call ``db.session.commit()``
|
||||
directly instead.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
Added the ``engine_options`` parameter.
|
||||
|
||||
@@ -129,9 +168,10 @@ class SQLAlchemy:
|
||||
metadata: sa.MetaData | None = None,
|
||||
session_options: dict[str, t.Any] | None = None,
|
||||
query_class: type[Query] = Query,
|
||||
model_class: type[Model] | sa_orm.DeclarativeMeta = Model,
|
||||
model_class: _FSA_MCT = Model, # type: ignore[assignment]
|
||||
engine_options: dict[str, t.Any] | None = None,
|
||||
add_models_to_shell: bool = True,
|
||||
disable_autonaming: bool = False,
|
||||
):
|
||||
if session_options is None:
|
||||
session_options = {}
|
||||
@@ -173,8 +213,17 @@ class SQLAlchemy:
|
||||
"""
|
||||
|
||||
if metadata is not None:
|
||||
metadata.info["bind_key"] = None
|
||||
self.metadatas[None] = metadata
|
||||
if len(_get_2x_declarative_bases(model_class)) > 0:
|
||||
warnings.warn(
|
||||
"When using SQLAlchemy 2.x style of declarative classes,"
|
||||
" the `metadata` should be an attribute of the base class."
|
||||
"The metadata passed into SQLAlchemy() is ignored.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
metadata.info["bind_key"] = None
|
||||
self.metadatas[None] = metadata
|
||||
|
||||
self.Table = self._make_table_class()
|
||||
"""A :class:`sqlalchemy.schema.Table` class that chooses a metadata
|
||||
@@ -192,7 +241,9 @@ class SQLAlchemy:
|
||||
This is a subclass of SQLAlchemy's ``Table`` rather than a function.
|
||||
"""
|
||||
|
||||
self.Model = self._make_declarative_base(model_class)
|
||||
self.Model = self._make_declarative_base(
|
||||
model_class, disable_autonaming=disable_autonaming
|
||||
)
|
||||
"""A SQLAlchemy declarative model class. Subclass this to define database
|
||||
models.
|
||||
|
||||
@@ -204,9 +255,15 @@ class SQLAlchemy:
|
||||
database engine. Otherwise, it will use the default :attr:`metadata` and
|
||||
:attr:`engine`. This is ignored if the model sets ``metadata`` or ``__table__``.
|
||||
|
||||
Customize this by subclassing :class:`.Model` and passing the ``model_class``
|
||||
parameter to the extension. A fully created declarative model class can be
|
||||
For code using the SQLAlchemy 1.x API, customize this model by subclassing
|
||||
:class:`.Model` and passing the ``model_class`` parameter to the extension.
|
||||
A fully created declarative model class can be
|
||||
passed as well, to use a custom metaclass.
|
||||
|
||||
For code using the SQLAlchemy 2.x API, customize this model by subclassing
|
||||
:class:`sqlalchemy.orm.DeclarativeBase` or
|
||||
:class:`sqlalchemy.orm.DeclarativeBaseNoMeta`
|
||||
and passing the ``model_class`` parameter to the extension.
|
||||
"""
|
||||
|
||||
if engine_options is None:
|
||||
@@ -258,25 +315,13 @@ class SQLAlchemy:
|
||||
)
|
||||
|
||||
app.extensions["sqlalchemy"] = self
|
||||
app.teardown_appcontext(self._teardown_session)
|
||||
|
||||
if self._add_models_to_shell:
|
||||
from .cli import add_models_to_shell
|
||||
|
||||
app.shell_context_processor(add_models_to_shell)
|
||||
|
||||
if app.config.get("SQLALCHEMY_COMMIT_ON_TEARDOWN", False):
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'SQLALCHEMY_COMMIT_ON_TEARDOWN' is deprecated and will be removed in"
|
||||
" Flask-SQAlchemy 3.1. Call 'db.session.commit()'` directly instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
app.teardown_appcontext(self._teardown_commit)
|
||||
else:
|
||||
app.teardown_appcontext(self._teardown_session)
|
||||
|
||||
basic_uri: str | sa.engine.URL | None = app.config.setdefault(
|
||||
"SQLALCHEMY_DATABASE_URI", None
|
||||
)
|
||||
@@ -393,20 +438,6 @@ class SQLAlchemy:
|
||||
options.setdefault("query_cls", self.Query)
|
||||
return sa_orm.sessionmaker(db=self, **options)
|
||||
|
||||
def _teardown_commit(self, exc: BaseException | None) -> None:
|
||||
"""Commit the session at the end of the request if there was not an unhandled
|
||||
exception during the request.
|
||||
|
||||
:meta private:
|
||||
|
||||
.. deprecated:: 3.0
|
||||
Will be removed in 3.1. Use ``db.session.commit()`` directly instead.
|
||||
"""
|
||||
if exc is None:
|
||||
self.session.commit()
|
||||
|
||||
self.session.remove()
|
||||
|
||||
def _teardown_session(self, exc: BaseException | None) -> None:
|
||||
"""Remove the current session at the end of the request.
|
||||
|
||||
@@ -464,29 +495,16 @@ class SQLAlchemy:
|
||||
if not args or (len(args) >= 2 and isinstance(args[1], sa.MetaData)):
|
||||
return super().__new__(cls, *args, **kwargs)
|
||||
|
||||
if (
|
||||
bind_key is None
|
||||
and "info" in kwargs
|
||||
and "bind_key" in kwargs["info"]
|
||||
):
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'table.info['bind_key'] is deprecated and will not be used in"
|
||||
" Flask-SQLAlchemy 3.1. Pass the 'bind_key' parameter instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
bind_key = kwargs["info"].get("bind_key")
|
||||
|
||||
metadata = self._make_metadata(bind_key)
|
||||
return super().__new__(cls, *[args[0], metadata, *args[1:]], **kwargs)
|
||||
|
||||
return Table
|
||||
|
||||
def _make_declarative_base(
|
||||
self, model: type[Model] | sa_orm.DeclarativeMeta
|
||||
) -> type[t.Any]:
|
||||
self,
|
||||
model_class: _FSA_MCT,
|
||||
disable_autonaming: bool = False,
|
||||
) -> t.Type[_FSAModel]:
|
||||
"""Create a SQLAlchemy declarative model class. The result is available as
|
||||
:attr:`Model`.
|
||||
|
||||
@@ -498,7 +516,14 @@ class SQLAlchemy:
|
||||
|
||||
:meta private:
|
||||
|
||||
:param model: A model base class, or an already created declarative model class.
|
||||
:param model_class: A model base class, or an already created declarative model
|
||||
class.
|
||||
|
||||
:param disable_autonaming: Turns off automatic tablename generation in models.
|
||||
|
||||
.. versionchanged:: 3.1.0
|
||||
Added support for passing SQLAlchemy 2.x base class as model class.
|
||||
Added optional ``disable_autonaming`` parameter.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Renamed with a leading underscore, this method is internal.
|
||||
@@ -506,22 +531,45 @@ class SQLAlchemy:
|
||||
.. versionchanged:: 2.3
|
||||
``model`` can be an already created declarative model class.
|
||||
"""
|
||||
if not isinstance(model, sa_orm.DeclarativeMeta):
|
||||
metadata = self._make_metadata(None)
|
||||
model = sa_orm.declarative_base(
|
||||
metadata=metadata, cls=model, name="Model", metaclass=DefaultMeta
|
||||
model: t.Type[_FSAModel]
|
||||
declarative_bases = _get_2x_declarative_bases(model_class)
|
||||
if len(declarative_bases) > 1:
|
||||
# raise error if more than one declarative base is found
|
||||
raise ValueError(
|
||||
"Only one declarative base can be passed to SQLAlchemy."
|
||||
" Got: {}".format(model_class.__bases__)
|
||||
)
|
||||
elif len(declarative_bases) == 1:
|
||||
body = dict(model_class.__dict__)
|
||||
body["__fsa__"] = self
|
||||
mixin_classes = [BindMixin, NameMixin, Model]
|
||||
if disable_autonaming:
|
||||
mixin_classes.remove(NameMixin)
|
||||
model = types.new_class(
|
||||
"FlaskSQLAlchemyBase",
|
||||
(*mixin_classes, *model_class.__bases__),
|
||||
{"metaclass": type(declarative_bases[0])},
|
||||
lambda ns: ns.update(body),
|
||||
)
|
||||
elif not isinstance(model_class, sa_orm.DeclarativeMeta):
|
||||
metadata = self._make_metadata(None)
|
||||
metaclass = DefaultMetaNoName if disable_autonaming else DefaultMeta
|
||||
model = sa_orm.declarative_base(
|
||||
metadata=metadata, cls=model_class, name="Model", metaclass=metaclass
|
||||
)
|
||||
else:
|
||||
model = model_class # type: ignore[assignment]
|
||||
|
||||
if None not in self.metadatas:
|
||||
# Use the model's metadata as the default metadata.
|
||||
model.metadata.info["bind_key"] = None # type: ignore[union-attr]
|
||||
self.metadatas[None] = model.metadata # type: ignore[union-attr]
|
||||
model.metadata.info["bind_key"] = None
|
||||
self.metadatas[None] = model.metadata
|
||||
else:
|
||||
# Use the passed in default metadata as the model's metadata.
|
||||
model.metadata = self.metadatas[None] # type: ignore[union-attr]
|
||||
model.metadata = self.metadatas[None]
|
||||
|
||||
model.query_class = self.Query
|
||||
model.query = _QueryProperty()
|
||||
model.query = _QueryProperty() # type: ignore[assignment]
|
||||
model.__fsa__ = self
|
||||
return model
|
||||
|
||||
@@ -660,80 +708,41 @@ class SQLAlchemy:
|
||||
"""
|
||||
return self.engines[None]
|
||||
|
||||
def get_engine(self, bind_key: str | None = None) -> sa.engine.Engine:
|
||||
def get_engine(
|
||||
self, bind_key: str | None = None, **kwargs: t.Any
|
||||
) -> sa.engine.Engine:
|
||||
"""Get the engine for the given bind key for the current application.
|
||||
|
||||
This requires that a Flask application context is active.
|
||||
|
||||
:param bind_key: The name of the engine.
|
||||
|
||||
.. deprecated:: 3.0
|
||||
Will be removed in Flask-SQLAlchemy 3.1. Use ``engines[key]`` instead.
|
||||
Will be removed in Flask-SQLAlchemy 3.2. Use ``engines[key]`` instead.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Renamed the ``bind`` parameter to ``bind_key``. Removed the ``app``
|
||||
parameter.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'get_engine' is deprecated and will be removed in Flask-SQLAlchemy 3.1."
|
||||
" Use 'engine' or 'engines[key]' instead.",
|
||||
"'get_engine' is deprecated and will be removed in Flask-SQLAlchemy"
|
||||
" 3.2. Use 'engine' or 'engines[key]' instead. If you're using"
|
||||
" Flask-Migrate or Alembic, you'll need to update your 'env.py' file.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if "bind" in kwargs:
|
||||
bind_key = kwargs.pop("bind")
|
||||
|
||||
return self.engines[bind_key]
|
||||
|
||||
def get_tables_for_bind(self, bind_key: str | None = None) -> list[sa.Table]:
|
||||
"""Get all tables in the metadata for the given bind key.
|
||||
|
||||
:param bind_key: The bind key to get.
|
||||
|
||||
.. deprecated:: 3.0
|
||||
Will be removed in Flask-SQLAlchemy 3.1. Use ``metadata.tables`` instead.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Renamed the ``bind`` parameter to ``bind_key``.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'get_tables_for_bind' is deprecated and will be removed in"
|
||||
" Flask-SQLAlchemy 3.1. Use 'metadata.tables' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return list(self.metadatas[bind_key].tables.values())
|
||||
|
||||
def get_binds(self) -> dict[sa.Table, sa.engine.Engine]:
|
||||
"""Map all tables to their engine based on their bind key, which can be used to
|
||||
create a session with ``Session(binds=db.get_binds(app))``.
|
||||
|
||||
This requires that a Flask application context is active.
|
||||
|
||||
.. deprecated:: 3.0
|
||||
Will be removed in Flask-SQLAlchemy 3.1. ``db.session`` supports multiple
|
||||
binds directly.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Removed the ``app`` parameter.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'get_binds' is deprecated and will be removed in Flask-SQLAlchemy 3.1."
|
||||
" 'db.session' supports multiple binds directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return {
|
||||
table: engine
|
||||
for bind_key, engine in self.engines.items()
|
||||
for table in self.metadatas[bind_key].tables.values()
|
||||
}
|
||||
|
||||
def get_or_404(
|
||||
self, entity: type[_O], ident: t.Any, *, description: str | None = None
|
||||
self,
|
||||
entity: type[_O],
|
||||
ident: t.Any,
|
||||
*,
|
||||
description: str | None = None,
|
||||
**kwargs: t.Any,
|
||||
) -> _O:
|
||||
"""Like :meth:`session.get() <sqlalchemy.orm.Session.get>` but aborts with a
|
||||
``404 Not Found`` error instead of returning ``None``.
|
||||
@@ -741,10 +750,14 @@ class SQLAlchemy:
|
||||
:param entity: The model class to query.
|
||||
:param ident: The primary key to query.
|
||||
:param description: A custom message to show on the error page.
|
||||
:param kwargs: Extra arguments passed to ``session.get()``.
|
||||
|
||||
.. versionchanged:: 3.1
|
||||
Pass extra keyword arguments to ``session.get()``.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
"""
|
||||
value = self.session.get(entity, ident)
|
||||
value = self.session.get(entity, ident, **kwargs)
|
||||
|
||||
if value is None:
|
||||
abort(404, description=description)
|
||||
@@ -974,24 +987,11 @@ class SQLAlchemy:
|
||||
.. versionchanged:: 3.0
|
||||
The :attr:`Query` class is set on ``backref``.
|
||||
"""
|
||||
# Deprecated, removed in SQLAlchemy 2.0. Accessed through ``__getattr__``.
|
||||
self._set_rel_query(kwargs)
|
||||
f = sa_orm.relationship
|
||||
return f(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name: str) -> t.Any:
|
||||
if name == "db":
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'db' attribute is deprecated and will be removed in"
|
||||
" Flask-SQLAlchemy 3.1. The extension is registered directly as"
|
||||
" 'app.extensions[\"sqlalchemy\"]'.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self
|
||||
|
||||
if name == "relation":
|
||||
return self._relation
|
||||
|
||||
|
||||
@@ -18,14 +18,6 @@ class _QueryProperty:
|
||||
:meta private:
|
||||
"""
|
||||
|
||||
@t.overload
|
||||
def __get__(self, obj: None, cls: type[Model]) -> Query:
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def __get__(self, obj: Model, cls: type[Model]) -> Query:
|
||||
...
|
||||
|
||||
def __get__(self, obj: Model | None, cls: type[Model]) -> Query:
|
||||
return cls.query_class(
|
||||
cls, session=cls.__fsa__.session() # type: ignore[arg-type]
|
||||
@@ -100,6 +92,38 @@ class BindMetaMixin(type):
|
||||
super().__init__(name, bases, d, **kwargs)
|
||||
|
||||
|
||||
class BindMixin:
|
||||
"""DeclarativeBase mixin to set a model's ``metadata`` based on ``__bind_key__``.
|
||||
|
||||
If no ``__bind_key__`` is specified, the model will use the default metadata
|
||||
provided by ``DeclarativeBase`` or ``DeclarativeBaseNoMeta``.
|
||||
If the model doesn't set ``metadata`` or ``__table__`` directly
|
||||
and does set ``__bind_key__``, the model will use the metadata
|
||||
for the specified bind key.
|
||||
If the ``metadata`` is the same as the parent model, it will not be set
|
||||
directly on the child model.
|
||||
|
||||
.. versionchanged:: 3.1.0
|
||||
"""
|
||||
|
||||
__fsa__: SQLAlchemy
|
||||
metadata: sa.MetaData
|
||||
|
||||
@classmethod
|
||||
def __init_subclass__(cls: t.Type[BindMixin], **kwargs: t.Dict[str, t.Any]) -> None:
|
||||
if not ("metadata" in cls.__dict__ or "__table__" in cls.__dict__) and hasattr(
|
||||
cls, "__bind_key__"
|
||||
):
|
||||
bind_key = getattr(cls, "__bind_key__", None)
|
||||
parent_metadata = getattr(cls, "metadata", None)
|
||||
metadata = cls.__fsa__._make_metadata(bind_key)
|
||||
|
||||
if metadata is not parent_metadata:
|
||||
cls.metadata = metadata
|
||||
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
|
||||
class NameMetaMixin(type):
|
||||
"""Metaclass mixin that sets a model's ``__tablename__`` by converting the
|
||||
``CamelCase`` class name to ``snake_case``. A name is set for non-abstract models
|
||||
@@ -169,6 +193,77 @@ class NameMetaMixin(type):
|
||||
return None
|
||||
|
||||
|
||||
class NameMixin:
|
||||
"""DeclarativeBase mixin that sets a model's ``__tablename__`` by converting the
|
||||
``CamelCase`` class name to ``snake_case``. A name is set for non-abstract models
|
||||
that do not otherwise define ``__tablename__``. If a model does not define a primary
|
||||
key, it will not generate a name or ``__table__``, for single-table inheritance.
|
||||
|
||||
.. versionchanged:: 3.1.0
|
||||
"""
|
||||
|
||||
metadata: sa.MetaData
|
||||
__tablename__: str
|
||||
__table__: sa.Table
|
||||
|
||||
@classmethod
|
||||
def __init_subclass__(cls: t.Type[NameMixin], **kwargs: t.Dict[str, t.Any]) -> None:
|
||||
if should_set_tablename(cls):
|
||||
cls.__tablename__ = camel_to_snake_case(cls.__name__)
|
||||
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
# __table_cls__ has run. If no table was created, use the parent table.
|
||||
if (
|
||||
"__tablename__" not in cls.__dict__
|
||||
and "__table__" in cls.__dict__
|
||||
and cls.__dict__["__table__"] is None
|
||||
):
|
||||
del cls.__table__
|
||||
|
||||
@classmethod
|
||||
def __table_cls__(cls, *args: t.Any, **kwargs: t.Any) -> sa.Table | None:
|
||||
"""This is called by SQLAlchemy during mapper setup. It determines the final
|
||||
table object that the model will use.
|
||||
|
||||
If no primary key is found, that indicates single-table inheritance, so no table
|
||||
will be created and ``__tablename__`` will be unset.
|
||||
"""
|
||||
schema = kwargs.get("schema")
|
||||
|
||||
if schema is None:
|
||||
key = args[0]
|
||||
else:
|
||||
key = f"{schema}.{args[0]}"
|
||||
|
||||
# Check if a table with this name already exists. Allows reflected tables to be
|
||||
# applied to models by name.
|
||||
if key in cls.metadata.tables:
|
||||
return sa.Table(*args, **kwargs)
|
||||
|
||||
# If a primary key is found, create a table for joined-table inheritance.
|
||||
for arg in args:
|
||||
if (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(
|
||||
arg, sa.PrimaryKeyConstraint
|
||||
):
|
||||
return sa.Table(*args, **kwargs)
|
||||
|
||||
# If no base classes define a table, return one that's missing a primary key
|
||||
# so SQLAlchemy shows the correct error.
|
||||
for base in cls.__mro__[1:-1]:
|
||||
if "__table__" in base.__dict__:
|
||||
break
|
||||
else:
|
||||
return sa.Table(*args, **kwargs)
|
||||
|
||||
# Single-table inheritance, use the parent table name. __init__ will unset
|
||||
# __table__ based on this.
|
||||
if "__tablename__" in cls.__dict__:
|
||||
del cls.__tablename__
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def should_set_tablename(cls: type) -> bool:
|
||||
"""Determine whether ``__tablename__`` should be generated for a model.
|
||||
|
||||
@@ -181,8 +276,16 @@ def should_set_tablename(cls: type) -> bool:
|
||||
Later, ``__table_cls__`` will determine if the model looks like single or
|
||||
joined-table inheritance. If no primary key is found, the name will be unset.
|
||||
"""
|
||||
if cls.__dict__.get("__abstract__", False) or not any(
|
||||
isinstance(b, sa_orm.DeclarativeMeta) for b in cls.__mro__[1:]
|
||||
if (
|
||||
cls.__dict__.get("__abstract__", False)
|
||||
or (
|
||||
not issubclass(cls, (sa_orm.DeclarativeBase, sa_orm.DeclarativeBaseNoMeta))
|
||||
and not any(isinstance(b, sa_orm.DeclarativeMeta) for b in cls.__mro__[1:])
|
||||
)
|
||||
or any(
|
||||
(b is sa_orm.DeclarativeBase or b is sa_orm.DeclarativeBaseNoMeta)
|
||||
for b in cls.__bases__
|
||||
)
|
||||
):
|
||||
return False
|
||||
|
||||
@@ -196,7 +299,14 @@ def should_set_tablename(cls: type) -> bool:
|
||||
return not (
|
||||
base is cls
|
||||
or base.__dict__.get("__abstract__", False)
|
||||
or not isinstance(base, sa_orm.DeclarativeMeta)
|
||||
or not (
|
||||
# SQLAlchemy 1.x
|
||||
isinstance(base, sa_orm.DeclarativeMeta)
|
||||
# 2.x: DeclarativeBas uses this as metaclass
|
||||
or isinstance(base, sa_orm.decl_api.DeclarativeAttributeIntercept)
|
||||
# 2.x: DeclarativeBaseNoMeta doesn't use a metaclass
|
||||
or issubclass(base, sa_orm.DeclarativeBaseNoMeta)
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -212,3 +322,9 @@ class DefaultMeta(BindMetaMixin, NameMetaMixin, sa_orm.DeclarativeMeta):
|
||||
"""SQLAlchemy declarative metaclass that provides ``__bind_key__`` and
|
||||
``__tablename__`` support.
|
||||
"""
|
||||
|
||||
|
||||
class DefaultMetaNoName(BindMetaMixin, sa_orm.DeclarativeMeta):
|
||||
"""SQLAlchemy declarative metaclass that provides ``__bind_key__`` and
|
||||
``__tablename__`` support.
|
||||
"""
|
||||
|
||||
@@ -70,30 +70,6 @@ class _QueryInfo:
|
||||
def duration(self) -> float:
|
||||
return self.end_time - self.start_time
|
||||
|
||||
@property
|
||||
def context(self) -> str:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'context' is renamed to 'location'. The old name is deprecated and will be"
|
||||
" removed in Flask-SQLAlchemy 3.1.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.location
|
||||
|
||||
def __getitem__(self, key: int) -> object:
|
||||
import warnings
|
||||
|
||||
name = ("statement", "parameters", "start_time", "end_time", "location")[key]
|
||||
warnings.warn(
|
||||
"Query info is a dataclass, not a tuple. Lookup by index is deprecated and"
|
||||
f" will be removed in Flask-SQLAlchemy 3.1. Use 'info.{name}' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return getattr(self, name)
|
||||
|
||||
|
||||
def _listen(engine: sa.engine.Engine) -> None:
|
||||
sa_event.listen(engine, "before_cursor_execute", _record_start, named=True)
|
||||
|
||||
@@ -79,13 +79,22 @@ class Session(sa_orm.Session):
|
||||
|
||||
|
||||
def _clause_to_engine(
|
||||
clause: t.Any | None, engines: t.Mapping[str | None, sa.engine.Engine]
|
||||
clause: sa.ClauseElement | None,
|
||||
engines: t.Mapping[str | None, sa.engine.Engine],
|
||||
) -> sa.engine.Engine | None:
|
||||
"""If the clause is a table, return the engine associated with the table's
|
||||
metadata's bind key.
|
||||
"""
|
||||
if isinstance(clause, sa.Table) and "bind_key" in clause.metadata.info:
|
||||
key = clause.metadata.info["bind_key"]
|
||||
table = None
|
||||
|
||||
if clause is not None:
|
||||
if isinstance(clause, sa.Table):
|
||||
table = clause
|
||||
elif isinstance(clause, sa.UpdateBase) and isinstance(clause.table, sa.Table):
|
||||
table = clause.table
|
||||
|
||||
if table is not None and "bind_key" in table.metadata.info:
|
||||
key = table.metadata.info["bind_key"]
|
||||
|
||||
if key not in engines:
|
||||
raise sa_exc.UnboundExecutionError(
|
||||
|
||||
Reference in New Issue
Block a user