]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
document declarative base made non-dynamically
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 20 Feb 2021 17:19:30 +0000 (12:19 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 25 Feb 2021 00:45:01 +0000 (19:45 -0500)
officially document how to make declarative base
non-dynamically such that mypy and similar tools
can process it without plugins

Change-Id: I884f9a7c06c4a8b8111948a2dd64e308e7dce4fc
References: #4609

doc/build/orm/mapping_styles.rst
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/decl_api.py
test/orm/declarative/test_basic.py

index 3abcafed35d97b3ab24e15a100c4b098f27c386a..622159bd73e96358ef479bc4ee258e67d0052296 100644 (file)
@@ -102,6 +102,30 @@ Documentation for Declarative mapping continues at :ref:`declarative_config_topl
 
     :ref:`declarative_config_toplevel`
 
+.. _orm_explicit_declarative_base:
+
+Creating an Explicit Base Non-Dynamically (for use with mypy, similar)
+----------------------------------------------------------------------
+
+Tools like mypy are not necessarily compatible with the dynamically
+generated ``Base`` delivered by SQLAlchemy functions like :func:`_orm.declarative_base`.
+To build a declarative base in a non-dynamic fashion, the
+:class:`_orm.DeclarativeMeta` class may be used directly as follows::
+
+    from sqlalchemy.orm import registry
+    from sqlalchemy.orm.decl_api import DeclarativeMeta
+
+    mapper_registry = registry()
+
+    class Base(metaclass=DeclarativeMeta):
+        __abstract__ = True
+        registry = mapper_registry
+        metadata = mapper_registry.metadata
+
+The above ``Base`` is equivalent to one created using the
+:meth:`_orm.registry.generate_base` method and will be fully understood by
+type analysis tools without the use of plugins.
+
 
 .. _orm_declarative_decorator:
 
index 4793fc6386bfa2b7c8aeeeb2d303ada8a64e8a45..ac06efba6b4931c957b299095480bb1dec118805 100644 (file)
@@ -22,6 +22,7 @@ from .attributes import QueryableAttribute  # noqa
 from .context import QueryContext  # noqa
 from .decl_api import as_declarative  # noqa
 from .decl_api import declarative_base  # noqa
+from .decl_api import DeclarativeMeta  # noqa
 from .decl_api import declared_attr  # noqa
 from .decl_api import has_inherited_table  # noqa
 from .decl_api import registry  # noqa
index 8afdb3a50bb8603b58d7360bf80ae3fd6c66d03b..1166d307e3878fbfc0299b3504eb1551473fc39f 100644 (file)
@@ -657,11 +657,23 @@ class registry(object):
                 __tablename__ = "my_table"
                 id = Column(Integer, primary_key=True)
 
+        The above dynamically generated class is equivalent to the
+        non-dynamic example below::
+
+            from sqlalchemy.orm import registry
+            from sqlalchemy.orm.decl_api import DeclarativeMeta
+
+            mapper_registry = registry()
+
+            class Base(metaclass=DeclarativeMeta):
+                __abstract__ = True
+                registry = mapper_registry
+                metadata = mapper_registry.metadata
+
         The :meth:`_orm.registry.generate_base` method provides the
         implementation for the :func:`_orm.declarative_base` function, which
         creates the :class:`_orm.registry` and base class all at once.
 
-
         See the section :ref:`orm_declarative_mapping` for background and
         examples.
 
index c779d214c873593af440a262b897124c4167b1e3..7fbd14b473ca0e356d9aec6260ed9ab9739264e8 100644 (file)
@@ -61,9 +61,20 @@ class DeclarativeTestBase(
 ):
     __dialect__ = "default"
 
+    base_style = "dynamic"
+
     def setup_test(self):
         global Base
-        Base = declarative_base(testing.db)
+
+        if self.base_style == "dynamic":
+            Base = declarative_base(testing.db)
+        elif self.base_style == "explicit":
+            mapper_registry = registry(_bind=testing.db)
+
+            class Base(with_metaclass(DeclarativeMeta)):
+                __abstract__ = True
+                registry = mapper_registry
+                metadata = mapper_registry.metadata
 
     def teardown_test(self):
         close_all_sessions()
@@ -71,6 +82,9 @@ class DeclarativeTestBase(
         Base.metadata.drop_all(testing.db)
 
 
+@testing.combinations(
+    ("dynamic",), ("explicit",), argnames="base_style", id_="s"
+)
 class DeclarativeTest(DeclarativeTestBase):
     def test_basic(self):
         class User(Base, fixtures.ComparableEntity):
@@ -2266,6 +2280,7 @@ class DeclarativeTest(DeclarativeTestBase):
         eq_(UserType._set_random_keyword_used_here, True)
 
 
+# TODO: this should be using @combinations
 def _produce_test(inline, stringbased):
     class ExplicitJoinTest(fixtures.MappedTest):
         @classmethod