summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py')
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py1658
1 files changed, 0 insertions, 1658 deletions
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py b/venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py
deleted file mode 100644
index bf6a5f2..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py
+++ /dev/null
@@ -1,1658 +0,0 @@
-# ext/automap.py
-# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-r"""Define an extension to the :mod:`sqlalchemy.ext.declarative` system
-which automatically generates mapped classes and relationships from a database
-schema, typically though not necessarily one which is reflected.
-
-It is hoped that the :class:`.AutomapBase` system provides a quick
-and modernized solution to the problem that the very famous
-`SQLSoup <https://sqlsoup.readthedocs.io/en/latest/>`_
-also tries to solve, that of generating a quick and rudimentary object
-model from an existing database on the fly. By addressing the issue strictly
-at the mapper configuration level, and integrating fully with existing
-Declarative class techniques, :class:`.AutomapBase` seeks to provide
-a well-integrated approach to the issue of expediently auto-generating ad-hoc
-mappings.
-
-.. tip:: The :ref:`automap_toplevel` extension is geared towards a
- "zero declaration" approach, where a complete ORM model including classes
- and pre-named relationships can be generated on the fly from a database
- schema. For applications that still want to use explicit class declarations
- including explicit relationship definitions in conjunction with reflection
- of tables, the :class:`.DeferredReflection` class, described at
- :ref:`orm_declarative_reflected_deferred_reflection`, is a better choice.
-
-.. _automap_basic_use:
-
-Basic Use
-=========
-
-The simplest usage is to reflect an existing database into a new model.
-We create a new :class:`.AutomapBase` class in a similar manner as to how
-we create a declarative base class, using :func:`.automap_base`.
-We then call :meth:`.AutomapBase.prepare` on the resulting base class,
-asking it to reflect the schema and produce mappings::
-
- from sqlalchemy.ext.automap import automap_base
- from sqlalchemy.orm import Session
- from sqlalchemy import create_engine
-
- Base = automap_base()
-
- # engine, suppose it has two tables 'user' and 'address' set up
- engine = create_engine("sqlite:///mydatabase.db")
-
- # reflect the tables
- Base.prepare(autoload_with=engine)
-
- # mapped classes are now created with names by default
- # matching that of the table name.
- User = Base.classes.user
- Address = Base.classes.address
-
- session = Session(engine)
-
- # rudimentary relationships are produced
- session.add(Address(email_address="foo@bar.com", user=User(name="foo")))
- session.commit()
-
- # collection-based relationships are by default named
- # "<classname>_collection"
- u1 = session.query(User).first()
- print (u1.address_collection)
-
-Above, calling :meth:`.AutomapBase.prepare` while passing along the
-:paramref:`.AutomapBase.prepare.reflect` parameter indicates that the
-:meth:`_schema.MetaData.reflect`
-method will be called on this declarative base
-classes' :class:`_schema.MetaData` collection; then, each **viable**
-:class:`_schema.Table` within the :class:`_schema.MetaData`
-will get a new mapped class
-generated automatically. The :class:`_schema.ForeignKeyConstraint`
-objects which
-link the various tables together will be used to produce new, bidirectional
-:func:`_orm.relationship` objects between classes.
-The classes and relationships
-follow along a default naming scheme that we can customize. At this point,
-our basic mapping consisting of related ``User`` and ``Address`` classes is
-ready to use in the traditional way.
-
-.. note:: By **viable**, we mean that for a table to be mapped, it must
- specify a primary key. Additionally, if the table is detected as being
- a pure association table between two other tables, it will not be directly
- mapped and will instead be configured as a many-to-many table between
- the mappings for the two referring tables.
-
-Generating Mappings from an Existing MetaData
-=============================================
-
-We can pass a pre-declared :class:`_schema.MetaData` object to
-:func:`.automap_base`.
-This object can be constructed in any way, including programmatically, from
-a serialized file, or from itself being reflected using
-:meth:`_schema.MetaData.reflect`.
-Below we illustrate a combination of reflection and
-explicit table declaration::
-
- from sqlalchemy import create_engine, MetaData, Table, Column, ForeignKey
- from sqlalchemy.ext.automap import automap_base
- engine = create_engine("sqlite:///mydatabase.db")
-
- # produce our own MetaData object
- metadata = MetaData()
-
- # we can reflect it ourselves from a database, using options
- # such as 'only' to limit what tables we look at...
- metadata.reflect(engine, only=['user', 'address'])
-
- # ... or just define our own Table objects with it (or combine both)
- Table('user_order', metadata,
- Column('id', Integer, primary_key=True),
- Column('user_id', ForeignKey('user.id'))
- )
-
- # we can then produce a set of mappings from this MetaData.
- Base = automap_base(metadata=metadata)
-
- # calling prepare() just sets up mapped classes and relationships.
- Base.prepare()
-
- # mapped classes are ready
- User, Address, Order = Base.classes.user, Base.classes.address,\
- Base.classes.user_order
-
-.. _automap_by_module:
-
-Generating Mappings from Multiple Schemas
-=========================================
-
-The :meth:`.AutomapBase.prepare` method when used with reflection may reflect
-tables from one schema at a time at most, using the
-:paramref:`.AutomapBase.prepare.schema` parameter to indicate the name of a
-schema to be reflected from. In order to populate the :class:`.AutomapBase`
-with tables from multiple schemas, :meth:`.AutomapBase.prepare` may be invoked
-multiple times, each time passing a different name to the
-:paramref:`.AutomapBase.prepare.schema` parameter. The
-:meth:`.AutomapBase.prepare` method keeps an internal list of
-:class:`_schema.Table` objects that have already been mapped, and will add new
-mappings only for those :class:`_schema.Table` objects that are new since the
-last time :meth:`.AutomapBase.prepare` was run::
-
- e = create_engine("postgresql://scott:tiger@localhost/test")
-
- Base.metadata.create_all(e)
-
- Base = automap_base()
-
- Base.prepare(e)
- Base.prepare(e, schema="test_schema")
- Base.prepare(e, schema="test_schema_2")
-
-.. versionadded:: 2.0 The :meth:`.AutomapBase.prepare` method may be called
- any number of times; only newly added tables will be mapped
- on each run. Previously in version 1.4 and earlier, multiple calls would
- cause errors as it would attempt to re-map an already mapped class.
- The previous workaround approach of invoking
- :meth:`_schema.MetaData.reflect` directly remains available as well.
-
-Automapping same-named tables across multiple schemas
------------------------------------------------------
-
-For the common case where multiple schemas may have same-named tables and
-therefore would generate same-named classes, conflicts can be resolved either
-through use of the :paramref:`.AutomapBase.prepare.classname_for_table` hook to
-apply different classnames on a per-schema basis, or by using the
-:paramref:`.AutomapBase.prepare.modulename_for_table` hook, which allows
-disambiguation of same-named classes by changing their effective ``__module__``
-attribute. In the example below, this hook is used to create a ``__module__``
-attribute for all classes that is of the form ``mymodule.<schemaname>``, where
-the schema name ``default`` is used if no schema is present::
-
- e = create_engine("postgresql://scott:tiger@localhost/test")
-
- Base.metadata.create_all(e)
-
- def module_name_for_table(cls, tablename, table):
- if table.schema is not None:
- return f"mymodule.{table.schema}"
- else:
- return f"mymodule.default"
-
- Base = automap_base()
-
- Base.prepare(e, modulename_for_table=module_name_for_table)
- Base.prepare(e, schema="test_schema", modulename_for_table=module_name_for_table)
- Base.prepare(e, schema="test_schema_2", modulename_for_table=module_name_for_table)
-
-
-The same named-classes are organized into a hierarchical collection available
-at :attr:`.AutomapBase.by_module`. This collection is traversed using the
-dot-separated name of a particular package/module down into the desired
-class name.
-
-.. note:: When using the :paramref:`.AutomapBase.prepare.modulename_for_table`
- hook to return a new ``__module__`` that is not ``None``, the class is
- **not** placed into the :attr:`.AutomapBase.classes` collection; only
- classes that were not given an explicit modulename are placed here, as the
- collection cannot represent same-named classes individually.
-
-In the example above, if the database contained a table named ``accounts`` in
-all three of the default schema, the ``test_schema`` schema, and the
-``test_schema_2`` schema, three separate classes will be available as::
-
- Base.by_module.mymodule.default.accounts
- Base.by_module.mymodule.test_schema.accounts
- Base.by_module.mymodule.test_schema_2.accounts
-
-The default module namespace generated for all :class:`.AutomapBase` classes is
-``sqlalchemy.ext.automap``. If no
-:paramref:`.AutomapBase.prepare.modulename_for_table` hook is used, the
-contents of :attr:`.AutomapBase.by_module` will be entirely within the
-``sqlalchemy.ext.automap`` namespace (e.g.
-``MyBase.by_module.sqlalchemy.ext.automap.<classname>``), which would contain
-the same series of classes as what would be seen in
-:attr:`.AutomapBase.classes`. Therefore it's generally only necessary to use
-:attr:`.AutomapBase.by_module` when explicit ``__module__`` conventions are
-present.
-
-.. versionadded: 2.0
-
- Added the :attr:`.AutomapBase.by_module` collection, which stores
- classes within a named hierarchy based on dot-separated module names,
- as well as the :paramref:`.Automap.prepare.modulename_for_table` parameter
- which allows for custom ``__module__`` schemes for automapped
- classes.
-
-
-
-Specifying Classes Explicitly
-=============================
-
-.. tip:: If explicit classes are expected to be prominent in an application,
- consider using :class:`.DeferredReflection` instead.
-
-The :mod:`.sqlalchemy.ext.automap` extension allows classes to be defined
-explicitly, in a way similar to that of the :class:`.DeferredReflection` class.
-Classes that extend from :class:`.AutomapBase` act like regular declarative
-classes, but are not immediately mapped after their construction, and are
-instead mapped when we call :meth:`.AutomapBase.prepare`. The
-:meth:`.AutomapBase.prepare` method will make use of the classes we've
-established based on the table name we use. If our schema contains tables
-``user`` and ``address``, we can define one or both of the classes to be used::
-
- from sqlalchemy.ext.automap import automap_base
- from sqlalchemy import create_engine
-
- # automap base
- Base = automap_base()
-
- # pre-declare User for the 'user' table
- class User(Base):
- __tablename__ = 'user'
-
- # override schema elements like Columns
- user_name = Column('name', String)
-
- # override relationships too, if desired.
- # we must use the same name that automap would use for the
- # relationship, and also must refer to the class name that automap will
- # generate for "address"
- address_collection = relationship("address", collection_class=set)
-
- # reflect
- engine = create_engine("sqlite:///mydatabase.db")
- Base.prepare(autoload_with=engine)
-
- # we still have Address generated from the tablename "address",
- # but User is the same as Base.classes.User now
-
- Address = Base.classes.address
-
- u1 = session.query(User).first()
- print (u1.address_collection)
-
- # the backref is still there:
- a1 = session.query(Address).first()
- print (a1.user)
-
-Above, one of the more intricate details is that we illustrated overriding
-one of the :func:`_orm.relationship` objects that automap would have created.
-To do this, we needed to make sure the names match up with what automap
-would normally generate, in that the relationship name would be
-``User.address_collection`` and the name of the class referred to, from
-automap's perspective, is called ``address``, even though we are referring to
-it as ``Address`` within our usage of this class.
-
-Overriding Naming Schemes
-=========================
-
-:mod:`.sqlalchemy.ext.automap` is tasked with producing mapped classes and
-relationship names based on a schema, which means it has decision points in how
-these names are determined. These three decision points are provided using
-functions which can be passed to the :meth:`.AutomapBase.prepare` method, and
-are known as :func:`.classname_for_table`,
-:func:`.name_for_scalar_relationship`,
-and :func:`.name_for_collection_relationship`. Any or all of these
-functions are provided as in the example below, where we use a "camel case"
-scheme for class names and a "pluralizer" for collection names using the
-`Inflect <https://pypi.org/project/inflect>`_ package::
-
- import re
- import inflect
-
- def camelize_classname(base, tablename, table):
- "Produce a 'camelized' class name, e.g. "
- "'words_and_underscores' -> 'WordsAndUnderscores'"
-
- return str(tablename[0].upper() + \
- re.sub(r'_([a-z])', lambda m: m.group(1).upper(), tablename[1:]))
-
- _pluralizer = inflect.engine()
- def pluralize_collection(base, local_cls, referred_cls, constraint):
- "Produce an 'uncamelized', 'pluralized' class name, e.g. "
- "'SomeTerm' -> 'some_terms'"
-
- referred_name = referred_cls.__name__
- uncamelized = re.sub(r'[A-Z]',
- lambda m: "_%s" % m.group(0).lower(),
- referred_name)[1:]
- pluralized = _pluralizer.plural(uncamelized)
- return pluralized
-
- from sqlalchemy.ext.automap import automap_base
-
- Base = automap_base()
-
- engine = create_engine("sqlite:///mydatabase.db")
-
- Base.prepare(autoload_with=engine,
- classname_for_table=camelize_classname,
- name_for_collection_relationship=pluralize_collection
- )
-
-From the above mapping, we would now have classes ``User`` and ``Address``,
-where the collection from ``User`` to ``Address`` is called
-``User.addresses``::
-
- User, Address = Base.classes.User, Base.classes.Address
-
- u1 = User(addresses=[Address(email="foo@bar.com")])
-
-Relationship Detection
-======================
-
-The vast majority of what automap accomplishes is the generation of
-:func:`_orm.relationship` structures based on foreign keys. The mechanism
-by which this works for many-to-one and one-to-many relationships is as
-follows:
-
-1. A given :class:`_schema.Table`, known to be mapped to a particular class,
- is examined for :class:`_schema.ForeignKeyConstraint` objects.
-
-2. From each :class:`_schema.ForeignKeyConstraint`, the remote
- :class:`_schema.Table`
- object present is matched up to the class to which it is to be mapped,
- if any, else it is skipped.
-
-3. As the :class:`_schema.ForeignKeyConstraint`
- we are examining corresponds to a
- reference from the immediate mapped class, the relationship will be set up
- as a many-to-one referring to the referred class; a corresponding
- one-to-many backref will be created on the referred class referring
- to this class.
-
-4. If any of the columns that are part of the
- :class:`_schema.ForeignKeyConstraint`
- are not nullable (e.g. ``nullable=False``), a
- :paramref:`_orm.relationship.cascade` keyword argument
- of ``all, delete-orphan`` will be added to the keyword arguments to
- be passed to the relationship or backref. If the
- :class:`_schema.ForeignKeyConstraint` reports that
- :paramref:`_schema.ForeignKeyConstraint.ondelete`
- is set to ``CASCADE`` for a not null or ``SET NULL`` for a nullable
- set of columns, the option :paramref:`_orm.relationship.passive_deletes`
- flag is set to ``True`` in the set of relationship keyword arguments.
- Note that not all backends support reflection of ON DELETE.
-
-5. The names of the relationships are determined using the
- :paramref:`.AutomapBase.prepare.name_for_scalar_relationship` and
- :paramref:`.AutomapBase.prepare.name_for_collection_relationship`
- callable functions. It is important to note that the default relationship
- naming derives the name from the **the actual class name**. If you've
- given a particular class an explicit name by declaring it, or specified an
- alternate class naming scheme, that's the name from which the relationship
- name will be derived.
-
-6. The classes are inspected for an existing mapped property matching these
- names. If one is detected on one side, but none on the other side,
- :class:`.AutomapBase` attempts to create a relationship on the missing side,
- then uses the :paramref:`_orm.relationship.back_populates`
- parameter in order to
- point the new relationship to the other side.
-
-7. In the usual case where no relationship is on either side,
- :meth:`.AutomapBase.prepare` produces a :func:`_orm.relationship` on the
- "many-to-one" side and matches it to the other using the
- :paramref:`_orm.relationship.backref` parameter.
-
-8. Production of the :func:`_orm.relationship` and optionally the
- :func:`.backref`
- is handed off to the :paramref:`.AutomapBase.prepare.generate_relationship`
- function, which can be supplied by the end-user in order to augment
- the arguments passed to :func:`_orm.relationship` or :func:`.backref` or to
- make use of custom implementations of these functions.
-
-Custom Relationship Arguments
------------------------------
-
-The :paramref:`.AutomapBase.prepare.generate_relationship` hook can be used
-to add parameters to relationships. For most cases, we can make use of the
-existing :func:`.automap.generate_relationship` function to return
-the object, after augmenting the given keyword dictionary with our own
-arguments.
-
-Below is an illustration of how to send
-:paramref:`_orm.relationship.cascade` and
-:paramref:`_orm.relationship.passive_deletes`
-options along to all one-to-many relationships::
-
- from sqlalchemy.ext.automap import generate_relationship
-
- def _gen_relationship(base, direction, return_fn,
- attrname, local_cls, referred_cls, **kw):
- if direction is interfaces.ONETOMANY:
- kw['cascade'] = 'all, delete-orphan'
- kw['passive_deletes'] = True
- # make use of the built-in function to actually return
- # the result.
- return generate_relationship(base, direction, return_fn,
- attrname, local_cls, referred_cls, **kw)
-
- from sqlalchemy.ext.automap import automap_base
- from sqlalchemy import create_engine
-
- # automap base
- Base = automap_base()
-
- engine = create_engine("sqlite:///mydatabase.db")
- Base.prepare(autoload_with=engine,
- generate_relationship=_gen_relationship)
-
-Many-to-Many relationships
---------------------------
-
-:mod:`.sqlalchemy.ext.automap` will generate many-to-many relationships, e.g.
-those which contain a ``secondary`` argument. The process for producing these
-is as follows:
-
-1. A given :class:`_schema.Table` is examined for
- :class:`_schema.ForeignKeyConstraint`
- objects, before any mapped class has been assigned to it.
-
-2. If the table contains two and exactly two
- :class:`_schema.ForeignKeyConstraint`
- objects, and all columns within this table are members of these two
- :class:`_schema.ForeignKeyConstraint` objects, the table is assumed to be a
- "secondary" table, and will **not be mapped directly**.
-
-3. The two (or one, for self-referential) external tables to which the
- :class:`_schema.Table`
- refers to are matched to the classes to which they will be
- mapped, if any.
-
-4. If mapped classes for both sides are located, a many-to-many bi-directional
- :func:`_orm.relationship` / :func:`.backref`
- pair is created between the two
- classes.
-
-5. The override logic for many-to-many works the same as that of one-to-many/
- many-to-one; the :func:`.generate_relationship` function is called upon
- to generate the structures and existing attributes will be maintained.
-
-Relationships with Inheritance
-------------------------------
-
-:mod:`.sqlalchemy.ext.automap` will not generate any relationships between
-two classes that are in an inheritance relationship. That is, with two
-classes given as follows::
-
- class Employee(Base):
- __tablename__ = 'employee'
- id = Column(Integer, primary_key=True)
- type = Column(String(50))
- __mapper_args__ = {
- 'polymorphic_identity':'employee', 'polymorphic_on': type
- }
-
- class Engineer(Employee):
- __tablename__ = 'engineer'
- id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
- __mapper_args__ = {
- 'polymorphic_identity':'engineer',
- }
-
-The foreign key from ``Engineer`` to ``Employee`` is used not for a
-relationship, but to establish joined inheritance between the two classes.
-
-Note that this means automap will not generate *any* relationships
-for foreign keys that link from a subclass to a superclass. If a mapping
-has actual relationships from subclass to superclass as well, those
-need to be explicit. Below, as we have two separate foreign keys
-from ``Engineer`` to ``Employee``, we need to set up both the relationship
-we want as well as the ``inherit_condition``, as these are not things
-SQLAlchemy can guess::
-
- class Employee(Base):
- __tablename__ = 'employee'
- id = Column(Integer, primary_key=True)
- type = Column(String(50))
-
- __mapper_args__ = {
- 'polymorphic_identity':'employee', 'polymorphic_on':type
- }
-
- class Engineer(Employee):
- __tablename__ = 'engineer'
- id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
- favorite_employee_id = Column(Integer, ForeignKey('employee.id'))
-
- favorite_employee = relationship(Employee,
- foreign_keys=favorite_employee_id)
-
- __mapper_args__ = {
- 'polymorphic_identity':'engineer',
- 'inherit_condition': id == Employee.id
- }
-
-Handling Simple Naming Conflicts
---------------------------------
-
-In the case of naming conflicts during mapping, override any of
-:func:`.classname_for_table`, :func:`.name_for_scalar_relationship`,
-and :func:`.name_for_collection_relationship` as needed. For example, if
-automap is attempting to name a many-to-one relationship the same as an
-existing column, an alternate convention can be conditionally selected. Given
-a schema:
-
-.. sourcecode:: sql
-
- CREATE TABLE table_a (
- id INTEGER PRIMARY KEY
- );
-
- CREATE TABLE table_b (
- id INTEGER PRIMARY KEY,
- table_a INTEGER,
- FOREIGN KEY(table_a) REFERENCES table_a(id)
- );
-
-The above schema will first automap the ``table_a`` table as a class named
-``table_a``; it will then automap a relationship onto the class for ``table_b``
-with the same name as this related class, e.g. ``table_a``. This
-relationship name conflicts with the mapping column ``table_b.table_a``,
-and will emit an error on mapping.
-
-We can resolve this conflict by using an underscore as follows::
-
- def name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
- name = referred_cls.__name__.lower()
- local_table = local_cls.__table__
- if name in local_table.columns:
- newname = name + "_"
- warnings.warn(
- "Already detected name %s present. using %s" %
- (name, newname))
- return newname
- return name
-
-
- Base.prepare(autoload_with=engine,
- name_for_scalar_relationship=name_for_scalar_relationship)
-
-Alternatively, we can change the name on the column side. The columns
-that are mapped can be modified using the technique described at
-:ref:`mapper_column_distinct_names`, by assigning the column explicitly
-to a new name::
-
- Base = automap_base()
-
- class TableB(Base):
- __tablename__ = 'table_b'
- _table_a = Column('table_a', ForeignKey('table_a.id'))
-
- Base.prepare(autoload_with=engine)
-
-
-Using Automap with Explicit Declarations
-========================================
-
-As noted previously, automap has no dependency on reflection, and can make
-use of any collection of :class:`_schema.Table` objects within a
-:class:`_schema.MetaData`
-collection. From this, it follows that automap can also be used
-generate missing relationships given an otherwise complete model that fully
-defines table metadata::
-
- from sqlalchemy.ext.automap import automap_base
- from sqlalchemy import Column, Integer, String, ForeignKey
-
- Base = automap_base()
-
- class User(Base):
- __tablename__ = 'user'
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
- class Address(Base):
- __tablename__ = 'address'
-
- id = Column(Integer, primary_key=True)
- email = Column(String)
- user_id = Column(ForeignKey('user.id'))
-
- # produce relationships
- Base.prepare()
-
- # mapping is complete, with "address_collection" and
- # "user" relationships
- a1 = Address(email='u1')
- a2 = Address(email='u2')
- u1 = User(address_collection=[a1, a2])
- assert a1.user is u1
-
-Above, given mostly complete ``User`` and ``Address`` mappings, the
-:class:`_schema.ForeignKey` which we defined on ``Address.user_id`` allowed a
-bidirectional relationship pair ``Address.user`` and
-``User.address_collection`` to be generated on the mapped classes.
-
-Note that when subclassing :class:`.AutomapBase`,
-the :meth:`.AutomapBase.prepare` method is required; if not called, the classes
-we've declared are in an un-mapped state.
-
-
-.. _automap_intercepting_columns:
-
-Intercepting Column Definitions
-===============================
-
-The :class:`_schema.MetaData` and :class:`_schema.Table` objects support an
-event hook :meth:`_events.DDLEvents.column_reflect` that may be used to intercept
-the information reflected about a database column before the :class:`_schema.Column`
-object is constructed. For example if we wanted to map columns using a
-naming convention such as ``"attr_<columnname>"``, the event could
-be applied as::
-
- @event.listens_for(Base.metadata, "column_reflect")
- def column_reflect(inspector, table, column_info):
- # set column.key = "attr_<lower_case_name>"
- column_info['key'] = "attr_%s" % column_info['name'].lower()
-
- # run reflection
- Base.prepare(autoload_with=engine)
-
-.. versionadded:: 1.4.0b2 the :meth:`_events.DDLEvents.column_reflect` event
- may be applied to a :class:`_schema.MetaData` object.
-
-.. seealso::
-
- :meth:`_events.DDLEvents.column_reflect`
-
- :ref:`mapper_automated_reflection_schemes` - in the ORM mapping documentation
-
-
-""" # noqa
-from __future__ import annotations
-
-import dataclasses
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import ClassVar
-from typing import Dict
-from typing import List
-from typing import NoReturn
-from typing import Optional
-from typing import overload
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-from .. import util
-from ..orm import backref
-from ..orm import declarative_base as _declarative_base
-from ..orm import exc as orm_exc
-from ..orm import interfaces
-from ..orm import relationship
-from ..orm.decl_base import _DeferredMapperConfig
-from ..orm.mapper import _CONFIGURE_MUTEX
-from ..schema import ForeignKeyConstraint
-from ..sql import and_
-from ..util import Properties
-from ..util.typing import Protocol
-
-if TYPE_CHECKING:
- from ..engine.base import Engine
- from ..orm.base import RelationshipDirection
- from ..orm.relationships import ORMBackrefArgument
- from ..orm.relationships import Relationship
- from ..sql.schema import Column
- from ..sql.schema import MetaData
- from ..sql.schema import Table
- from ..util import immutabledict
-
-
-_KT = TypeVar("_KT", bound=Any)
-_VT = TypeVar("_VT", bound=Any)
-
-
-class PythonNameForTableType(Protocol):
- def __call__(
- self, base: Type[Any], tablename: str, table: Table
- ) -> str: ...
-
-
-def classname_for_table(
- base: Type[Any],
- tablename: str,
- table: Table,
-) -> str:
- """Return the class name that should be used, given the name
- of a table.
-
- The default implementation is::
-
- return str(tablename)
-
- Alternate implementations can be specified using the
- :paramref:`.AutomapBase.prepare.classname_for_table`
- parameter.
-
- :param base: the :class:`.AutomapBase` class doing the prepare.
-
- :param tablename: string name of the :class:`_schema.Table`.
-
- :param table: the :class:`_schema.Table` object itself.
-
- :return: a string class name.
-
- .. note::
-
- In Python 2, the string used for the class name **must** be a
- non-Unicode object, e.g. a ``str()`` object. The ``.name`` attribute
- of :class:`_schema.Table` is typically a Python unicode subclass,
- so the
- ``str()`` function should be applied to this name, after accounting for
- any non-ASCII characters.
-
- """
- return str(tablename)
-
-
-class NameForScalarRelationshipType(Protocol):
- def __call__(
- self,
- base: Type[Any],
- local_cls: Type[Any],
- referred_cls: Type[Any],
- constraint: ForeignKeyConstraint,
- ) -> str: ...
-
-
-def name_for_scalar_relationship(
- base: Type[Any],
- local_cls: Type[Any],
- referred_cls: Type[Any],
- constraint: ForeignKeyConstraint,
-) -> str:
- """Return the attribute name that should be used to refer from one
- class to another, for a scalar object reference.
-
- The default implementation is::
-
- return referred_cls.__name__.lower()
-
- Alternate implementations can be specified using the
- :paramref:`.AutomapBase.prepare.name_for_scalar_relationship`
- parameter.
-
- :param base: the :class:`.AutomapBase` class doing the prepare.
-
- :param local_cls: the class to be mapped on the local side.
-
- :param referred_cls: the class to be mapped on the referring side.
-
- :param constraint: the :class:`_schema.ForeignKeyConstraint` that is being
- inspected to produce this relationship.
-
- """
- return referred_cls.__name__.lower()
-
-
-class NameForCollectionRelationshipType(Protocol):
- def __call__(
- self,
- base: Type[Any],
- local_cls: Type[Any],
- referred_cls: Type[Any],
- constraint: ForeignKeyConstraint,
- ) -> str: ...
-
-
-def name_for_collection_relationship(
- base: Type[Any],
- local_cls: Type[Any],
- referred_cls: Type[Any],
- constraint: ForeignKeyConstraint,
-) -> str:
- """Return the attribute name that should be used to refer from one
- class to another, for a collection reference.
-
- The default implementation is::
-
- return referred_cls.__name__.lower() + "_collection"
-
- Alternate implementations
- can be specified using the
- :paramref:`.AutomapBase.prepare.name_for_collection_relationship`
- parameter.
-
- :param base: the :class:`.AutomapBase` class doing the prepare.
-
- :param local_cls: the class to be mapped on the local side.
-
- :param referred_cls: the class to be mapped on the referring side.
-
- :param constraint: the :class:`_schema.ForeignKeyConstraint` that is being
- inspected to produce this relationship.
-
- """
- return referred_cls.__name__.lower() + "_collection"
-
-
-class GenerateRelationshipType(Protocol):
- @overload
- def __call__(
- self,
- base: Type[Any],
- direction: RelationshipDirection,
- return_fn: Callable[..., Relationship[Any]],
- attrname: str,
- local_cls: Type[Any],
- referred_cls: Type[Any],
- **kw: Any,
- ) -> Relationship[Any]: ...
-
- @overload
- def __call__(
- self,
- base: Type[Any],
- direction: RelationshipDirection,
- return_fn: Callable[..., ORMBackrefArgument],
- attrname: str,
- local_cls: Type[Any],
- referred_cls: Type[Any],
- **kw: Any,
- ) -> ORMBackrefArgument: ...
-
- def __call__(
- self,
- base: Type[Any],
- direction: RelationshipDirection,
- return_fn: Union[
- Callable[..., Relationship[Any]], Callable[..., ORMBackrefArgument]
- ],
- attrname: str,
- local_cls: Type[Any],
- referred_cls: Type[Any],
- **kw: Any,
- ) -> Union[ORMBackrefArgument, Relationship[Any]]: ...
-
-
-@overload
-def generate_relationship(
- base: Type[Any],
- direction: RelationshipDirection,
- return_fn: Callable[..., Relationship[Any]],
- attrname: str,
- local_cls: Type[Any],
- referred_cls: Type[Any],
- **kw: Any,
-) -> Relationship[Any]: ...
-
-
-@overload
-def generate_relationship(
- base: Type[Any],
- direction: RelationshipDirection,
- return_fn: Callable[..., ORMBackrefArgument],
- attrname: str,
- local_cls: Type[Any],
- referred_cls: Type[Any],
- **kw: Any,
-) -> ORMBackrefArgument: ...
-
-
-def generate_relationship(
- base: Type[Any],
- direction: RelationshipDirection,
- return_fn: Union[
- Callable[..., Relationship[Any]], Callable[..., ORMBackrefArgument]
- ],
- attrname: str,
- local_cls: Type[Any],
- referred_cls: Type[Any],
- **kw: Any,
-) -> Union[Relationship[Any], ORMBackrefArgument]:
- r"""Generate a :func:`_orm.relationship` or :func:`.backref`
- on behalf of two
- mapped classes.
-
- An alternate implementation of this function can be specified using the
- :paramref:`.AutomapBase.prepare.generate_relationship` parameter.
-
- The default implementation of this function is as follows::
-
- if return_fn is backref:
- return return_fn(attrname, **kw)
- elif return_fn is relationship:
- return return_fn(referred_cls, **kw)
- else:
- raise TypeError("Unknown relationship function: %s" % return_fn)
-
- :param base: the :class:`.AutomapBase` class doing the prepare.
-
- :param direction: indicate the "direction" of the relationship; this will
- be one of :data:`.ONETOMANY`, :data:`.MANYTOONE`, :data:`.MANYTOMANY`.
-
- :param return_fn: the function that is used by default to create the
- relationship. This will be either :func:`_orm.relationship` or
- :func:`.backref`. The :func:`.backref` function's result will be used to
- produce a new :func:`_orm.relationship` in a second step,
- so it is critical
- that user-defined implementations correctly differentiate between the two
- functions, if a custom relationship function is being used.
-
- :param attrname: the attribute name to which this relationship is being
- assigned. If the value of :paramref:`.generate_relationship.return_fn` is
- the :func:`.backref` function, then this name is the name that is being
- assigned to the backref.
-
- :param local_cls: the "local" class to which this relationship or backref
- will be locally present.
-
- :param referred_cls: the "referred" class to which the relationship or
- backref refers to.
-
- :param \**kw: all additional keyword arguments are passed along to the
- function.
-
- :return: a :func:`_orm.relationship` or :func:`.backref` construct,
- as dictated
- by the :paramref:`.generate_relationship.return_fn` parameter.
-
- """
-
- if return_fn is backref:
- return return_fn(attrname, **kw)
- elif return_fn is relationship:
- return return_fn(referred_cls, **kw)
- else:
- raise TypeError("Unknown relationship function: %s" % return_fn)
-
-
-ByModuleProperties = Properties[Union["ByModuleProperties", Type[Any]]]
-
-
-class AutomapBase:
- """Base class for an "automap" schema.
-
- The :class:`.AutomapBase` class can be compared to the "declarative base"
- class that is produced by the :func:`.declarative.declarative_base`
- function. In practice, the :class:`.AutomapBase` class is always used
- as a mixin along with an actual declarative base.
-
- A new subclassable :class:`.AutomapBase` is typically instantiated
- using the :func:`.automap_base` function.
-
- .. seealso::
-
- :ref:`automap_toplevel`
-
- """
-
- __abstract__ = True
-
- classes: ClassVar[Properties[Type[Any]]]
- """An instance of :class:`.util.Properties` containing classes.
-
- This object behaves much like the ``.c`` collection on a table. Classes
- are present under the name they were given, e.g.::
-
- Base = automap_base()
- Base.prepare(autoload_with=some_engine)
-
- User, Address = Base.classes.User, Base.classes.Address
-
- For class names that overlap with a method name of
- :class:`.util.Properties`, such as ``items()``, the getitem form
- is also supported::
-
- Item = Base.classes["items"]
-
- """
-
- by_module: ClassVar[ByModuleProperties]
- """An instance of :class:`.util.Properties` containing a hierarchal
- structure of dot-separated module names linked to classes.
-
- This collection is an alternative to the :attr:`.AutomapBase.classes`
- collection that is useful when making use of the
- :paramref:`.AutomapBase.prepare.modulename_for_table` parameter, which will
- apply distinct ``__module__`` attributes to generated classes.
-
- The default ``__module__`` an automap-generated class is
- ``sqlalchemy.ext.automap``; to access this namespace using
- :attr:`.AutomapBase.by_module` looks like::
-
- User = Base.by_module.sqlalchemy.ext.automap.User
-
- If a class had a ``__module__`` of ``mymodule.account``, accessing
- this namespace looks like::
-
- MyClass = Base.by_module.mymodule.account.MyClass
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`automap_by_module`
-
- """
-
- metadata: ClassVar[MetaData]
- """Refers to the :class:`_schema.MetaData` collection that will be used
- for new :class:`_schema.Table` objects.
-
- .. seealso::
-
- :ref:`orm_declarative_metadata`
-
- """
-
- _sa_automapbase_bookkeeping: ClassVar[_Bookkeeping]
-
- @classmethod
- @util.deprecated_params(
- engine=(
- "2.0",
- "The :paramref:`_automap.AutomapBase.prepare.engine` parameter "
- "is deprecated and will be removed in a future release. "
- "Please use the "
- ":paramref:`_automap.AutomapBase.prepare.autoload_with` "
- "parameter.",
- ),
- reflect=(
- "2.0",
- "The :paramref:`_automap.AutomapBase.prepare.reflect` "
- "parameter is deprecated and will be removed in a future "
- "release. Reflection is enabled when "
- ":paramref:`_automap.AutomapBase.prepare.autoload_with` "
- "is passed.",
- ),
- )
- def prepare(
- cls: Type[AutomapBase],
- autoload_with: Optional[Engine] = None,
- engine: Optional[Any] = None,
- reflect: bool = False,
- schema: Optional[str] = None,
- classname_for_table: Optional[PythonNameForTableType] = None,
- modulename_for_table: Optional[PythonNameForTableType] = None,
- collection_class: Optional[Any] = None,
- name_for_scalar_relationship: Optional[
- NameForScalarRelationshipType
- ] = None,
- name_for_collection_relationship: Optional[
- NameForCollectionRelationshipType
- ] = None,
- generate_relationship: Optional[GenerateRelationshipType] = None,
- reflection_options: Union[
- Dict[_KT, _VT], immutabledict[_KT, _VT]
- ] = util.EMPTY_DICT,
- ) -> None:
- """Extract mapped classes and relationships from the
- :class:`_schema.MetaData` and perform mappings.
-
- For full documentation and examples see
- :ref:`automap_basic_use`.
-
- :param autoload_with: an :class:`_engine.Engine` or
- :class:`_engine.Connection` with which
- to perform schema reflection; when specified, the
- :meth:`_schema.MetaData.reflect` method will be invoked within
- the scope of this method.
-
- :param engine: legacy; use :paramref:`.AutomapBase.autoload_with`.
- Used to indicate the :class:`_engine.Engine` or
- :class:`_engine.Connection` with which to reflect tables with,
- if :paramref:`.AutomapBase.reflect` is True.
-
- :param reflect: legacy; use :paramref:`.AutomapBase.autoload_with`.
- Indicates that :meth:`_schema.MetaData.reflect` should be invoked.
-
- :param classname_for_table: callable function which will be used to
- produce new class names, given a table name. Defaults to
- :func:`.classname_for_table`.
-
- :param modulename_for_table: callable function which will be used to
- produce the effective ``__module__`` for an internally generated
- class, to allow for multiple classes of the same name in a single
- automap base which would be in different "modules".
-
- Defaults to ``None``, which will indicate that ``__module__`` will not
- be set explicitly; the Python runtime will use the value
- ``sqlalchemy.ext.automap`` for these classes.
-
- When assigning ``__module__`` to generated classes, they can be
- accessed based on dot-separated module names using the
- :attr:`.AutomapBase.by_module` collection. Classes that have
- an explicit ``__module_`` assigned using this hook do **not** get
- placed into the :attr:`.AutomapBase.classes` collection, only
- into :attr:`.AutomapBase.by_module`.
-
- .. versionadded:: 2.0
-
- .. seealso::
-
- :ref:`automap_by_module`
-
- :param name_for_scalar_relationship: callable function which will be
- used to produce relationship names for scalar relationships. Defaults
- to :func:`.name_for_scalar_relationship`.
-
- :param name_for_collection_relationship: callable function which will
- be used to produce relationship names for collection-oriented
- relationships. Defaults to :func:`.name_for_collection_relationship`.
-
- :param generate_relationship: callable function which will be used to
- actually generate :func:`_orm.relationship` and :func:`.backref`
- constructs. Defaults to :func:`.generate_relationship`.
-
- :param collection_class: the Python collection class that will be used
- when a new :func:`_orm.relationship`
- object is created that represents a
- collection. Defaults to ``list``.
-
- :param schema: Schema name to reflect when reflecting tables using
- the :paramref:`.AutomapBase.prepare.autoload_with` parameter. The name
- is passed to the :paramref:`_schema.MetaData.reflect.schema` parameter
- of :meth:`_schema.MetaData.reflect`. When omitted, the default schema
- in use by the database connection is used.
-
- .. note:: The :paramref:`.AutomapBase.prepare.schema`
- parameter supports reflection of a single schema at a time.
- In order to include tables from many schemas, use
- multiple calls to :meth:`.AutomapBase.prepare`.
-
- For an overview of multiple-schema automap including the use
- of additional naming conventions to resolve table name
- conflicts, see the section :ref:`automap_by_module`.
-
- .. versionadded:: 2.0 :meth:`.AutomapBase.prepare` supports being
- directly invoked any number of times, keeping track of tables
- that have already been processed to avoid processing them
- a second time.
-
- :param reflection_options: When present, this dictionary of options
- will be passed to :meth:`_schema.MetaData.reflect`
- to supply general reflection-specific options like ``only`` and/or
- dialect-specific options like ``oracle_resolve_synonyms``.
-
- .. versionadded:: 1.4
-
- """
-
- for mr in cls.__mro__:
- if "_sa_automapbase_bookkeeping" in mr.__dict__:
- automap_base = cast("Type[AutomapBase]", mr)
- break
- else:
- assert False, "Can't locate automap base in class hierarchy"
-
- glbls = globals()
- if classname_for_table is None:
- classname_for_table = glbls["classname_for_table"]
- if name_for_scalar_relationship is None:
- name_for_scalar_relationship = glbls[
- "name_for_scalar_relationship"
- ]
- if name_for_collection_relationship is None:
- name_for_collection_relationship = glbls[
- "name_for_collection_relationship"
- ]
- if generate_relationship is None:
- generate_relationship = glbls["generate_relationship"]
- if collection_class is None:
- collection_class = list
-
- if autoload_with:
- reflect = True
-
- if engine:
- autoload_with = engine
-
- if reflect:
- assert autoload_with
- opts = dict(
- schema=schema,
- extend_existing=True,
- autoload_replace=False,
- )
- if reflection_options:
- opts.update(reflection_options)
- cls.metadata.reflect(autoload_with, **opts) # type: ignore[arg-type] # noqa: E501
-
- with _CONFIGURE_MUTEX:
- table_to_map_config: Union[
- Dict[Optional[Table], _DeferredMapperConfig],
- Dict[Table, _DeferredMapperConfig],
- ] = {
- cast("Table", m.local_table): m
- for m in _DeferredMapperConfig.classes_for_base(
- cls, sort=False
- )
- }
-
- many_to_many: List[
- Tuple[Table, Table, List[ForeignKeyConstraint], Table]
- ]
- many_to_many = []
-
- bookkeeping = automap_base._sa_automapbase_bookkeeping
- metadata_tables = cls.metadata.tables
-
- for table_key in set(metadata_tables).difference(
- bookkeeping.table_keys
- ):
- table = metadata_tables[table_key]
- bookkeeping.table_keys.add(table_key)
-
- lcl_m2m, rem_m2m, m2m_const = _is_many_to_many(cls, table)
- if lcl_m2m is not None:
- assert rem_m2m is not None
- assert m2m_const is not None
- many_to_many.append((lcl_m2m, rem_m2m, m2m_const, table))
- elif not table.primary_key:
- continue
- elif table not in table_to_map_config:
- clsdict: Dict[str, Any] = {"__table__": table}
- if modulename_for_table is not None:
- new_module = modulename_for_table(
- cls, table.name, table
- )
- if new_module is not None:
- clsdict["__module__"] = new_module
- else:
- new_module = None
-
- newname = classname_for_table(cls, table.name, table)
- if new_module is None and newname in cls.classes:
- util.warn(
- "Ignoring duplicate class name "
- f"'{newname}' "
- "received in automap base for table "
- f"{table.key} without "
- "``__module__`` being set; consider using the "
- "``modulename_for_table`` hook"
- )
- continue
-
- mapped_cls = type(
- newname,
- (automap_base,),
- clsdict,
- )
- map_config = _DeferredMapperConfig.config_for_cls(
- mapped_cls
- )
- assert map_config.cls.__name__ == newname
- if new_module is None:
- cls.classes[newname] = mapped_cls
-
- by_module_properties: ByModuleProperties = cls.by_module
- for token in map_config.cls.__module__.split("."):
- if token not in by_module_properties:
- by_module_properties[token] = util.Properties({})
-
- props = by_module_properties[token]
-
- # we can assert this because the clsregistry
- # module would have raised if there was a mismatch
- # between modules/classes already.
- # see test_cls_schema_name_conflict
- assert isinstance(props, Properties)
- by_module_properties = props
-
- by_module_properties[map_config.cls.__name__] = mapped_cls
-
- table_to_map_config[table] = map_config
-
- for map_config in table_to_map_config.values():
- _relationships_for_fks(
- automap_base,
- map_config,
- table_to_map_config,
- collection_class,
- name_for_scalar_relationship,
- name_for_collection_relationship,
- generate_relationship,
- )
-
- for lcl_m2m, rem_m2m, m2m_const, table in many_to_many:
- _m2m_relationship(
- automap_base,
- lcl_m2m,
- rem_m2m,
- m2m_const,
- table,
- table_to_map_config,
- collection_class,
- name_for_scalar_relationship,
- name_for_collection_relationship,
- generate_relationship,
- )
-
- for map_config in _DeferredMapperConfig.classes_for_base(
- automap_base
- ):
- map_config.map()
-
- _sa_decl_prepare = True
- """Indicate that the mapping of classes should be deferred.
-
- The presence of this attribute name indicates to declarative
- that the call to mapper() should not occur immediately; instead,
- information about the table and attributes to be mapped are gathered
- into an internal structure called _DeferredMapperConfig. These
- objects can be collected later using classes_for_base(), additional
- mapping decisions can be made, and then the map() method will actually
- apply the mapping.
-
- The only real reason this deferral of the whole
- thing is needed is to support primary key columns that aren't reflected
- yet when the class is declared; everything else can theoretically be
- added to the mapper later. However, the _DeferredMapperConfig is a
- nice interface in any case which exists at that not usually exposed point
- at which declarative has the class and the Table but hasn't called
- mapper() yet.
-
- """
-
- @classmethod
- def _sa_raise_deferred_config(cls) -> NoReturn:
- raise orm_exc.UnmappedClassError(
- cls,
- msg="Class %s is a subclass of AutomapBase. "
- "Mappings are not produced until the .prepare() "
- "method is called on the class hierarchy."
- % orm_exc._safe_cls_name(cls),
- )
-
-
-@dataclasses.dataclass
-class _Bookkeeping:
- __slots__ = ("table_keys",)
-
- table_keys: Set[str]
-
-
-def automap_base(
- declarative_base: Optional[Type[Any]] = None, **kw: Any
-) -> Any:
- r"""Produce a declarative automap base.
-
- This function produces a new base class that is a product of the
- :class:`.AutomapBase` class as well a declarative base produced by
- :func:`.declarative.declarative_base`.
-
- All parameters other than ``declarative_base`` are keyword arguments
- that are passed directly to the :func:`.declarative.declarative_base`
- function.
-
- :param declarative_base: an existing class produced by
- :func:`.declarative.declarative_base`. When this is passed, the function
- no longer invokes :func:`.declarative.declarative_base` itself, and all
- other keyword arguments are ignored.
-
- :param \**kw: keyword arguments are passed along to
- :func:`.declarative.declarative_base`.
-
- """
- if declarative_base is None:
- Base = _declarative_base(**kw)
- else:
- Base = declarative_base
-
- return type(
- Base.__name__,
- (AutomapBase, Base),
- {
- "__abstract__": True,
- "classes": util.Properties({}),
- "by_module": util.Properties({}),
- "_sa_automapbase_bookkeeping": _Bookkeeping(set()),
- },
- )
-
-
-def _is_many_to_many(
- automap_base: Type[Any], table: Table
-) -> Tuple[
- Optional[Table], Optional[Table], Optional[list[ForeignKeyConstraint]]
-]:
- fk_constraints = [
- const
- for const in table.constraints
- if isinstance(const, ForeignKeyConstraint)
- ]
- if len(fk_constraints) != 2:
- return None, None, None
-
- cols: List[Column[Any]] = sum(
- [
- [fk.parent for fk in fk_constraint.elements]
- for fk_constraint in fk_constraints
- ],
- [],
- )
-
- if set(cols) != set(table.c):
- return None, None, None
-
- return (
- fk_constraints[0].elements[0].column.table,
- fk_constraints[1].elements[0].column.table,
- fk_constraints,
- )
-
-
-def _relationships_for_fks(
- automap_base: Type[Any],
- map_config: _DeferredMapperConfig,
- table_to_map_config: Union[
- Dict[Optional[Table], _DeferredMapperConfig],
- Dict[Table, _DeferredMapperConfig],
- ],
- collection_class: type,
- name_for_scalar_relationship: NameForScalarRelationshipType,
- name_for_collection_relationship: NameForCollectionRelationshipType,
- generate_relationship: GenerateRelationshipType,
-) -> None:
- local_table = cast("Optional[Table]", map_config.local_table)
- local_cls = cast(
- "Optional[Type[Any]]", map_config.cls
- ) # derived from a weakref, may be None
-
- if local_table is None or local_cls is None:
- return
- for constraint in local_table.constraints:
- if isinstance(constraint, ForeignKeyConstraint):
- fks = constraint.elements
- referred_table = fks[0].column.table
- referred_cfg = table_to_map_config.get(referred_table, None)
- if referred_cfg is None:
- continue
- referred_cls = referred_cfg.cls
-
- if local_cls is not referred_cls and issubclass(
- local_cls, referred_cls
- ):
- continue
-
- relationship_name = name_for_scalar_relationship(
- automap_base, local_cls, referred_cls, constraint
- )
- backref_name = name_for_collection_relationship(
- automap_base, referred_cls, local_cls, constraint
- )
-
- o2m_kws: Dict[str, Union[str, bool]] = {}
- nullable = False not in {fk.parent.nullable for fk in fks}
- if not nullable:
- o2m_kws["cascade"] = "all, delete-orphan"
-
- if (
- constraint.ondelete
- and constraint.ondelete.lower() == "cascade"
- ):
- o2m_kws["passive_deletes"] = True
- else:
- if (
- constraint.ondelete
- and constraint.ondelete.lower() == "set null"
- ):
- o2m_kws["passive_deletes"] = True
-
- create_backref = backref_name not in referred_cfg.properties
-
- if relationship_name not in map_config.properties:
- if create_backref:
- backref_obj = generate_relationship(
- automap_base,
- interfaces.ONETOMANY,
- backref,
- backref_name,
- referred_cls,
- local_cls,
- collection_class=collection_class,
- **o2m_kws,
- )
- else:
- backref_obj = None
- rel = generate_relationship(
- automap_base,
- interfaces.MANYTOONE,
- relationship,
- relationship_name,
- local_cls,
- referred_cls,
- foreign_keys=[fk.parent for fk in constraint.elements],
- backref=backref_obj,
- remote_side=[fk.column for fk in constraint.elements],
- )
- if rel is not None:
- map_config.properties[relationship_name] = rel
- if not create_backref:
- referred_cfg.properties[
- backref_name
- ].back_populates = relationship_name # type: ignore[union-attr] # noqa: E501
- elif create_backref:
- rel = generate_relationship(
- automap_base,
- interfaces.ONETOMANY,
- relationship,
- backref_name,
- referred_cls,
- local_cls,
- foreign_keys=[fk.parent for fk in constraint.elements],
- back_populates=relationship_name,
- collection_class=collection_class,
- **o2m_kws,
- )
- if rel is not None:
- referred_cfg.properties[backref_name] = rel
- map_config.properties[
- relationship_name
- ].back_populates = backref_name # type: ignore[union-attr]
-
-
-def _m2m_relationship(
- automap_base: Type[Any],
- lcl_m2m: Table,
- rem_m2m: Table,
- m2m_const: List[ForeignKeyConstraint],
- table: Table,
- table_to_map_config: Union[
- Dict[Optional[Table], _DeferredMapperConfig],
- Dict[Table, _DeferredMapperConfig],
- ],
- collection_class: type,
- name_for_scalar_relationship: NameForCollectionRelationshipType,
- name_for_collection_relationship: NameForCollectionRelationshipType,
- generate_relationship: GenerateRelationshipType,
-) -> None:
- map_config = table_to_map_config.get(lcl_m2m, None)
- referred_cfg = table_to_map_config.get(rem_m2m, None)
- if map_config is None or referred_cfg is None:
- return
-
- local_cls = map_config.cls
- referred_cls = referred_cfg.cls
-
- relationship_name = name_for_collection_relationship(
- automap_base, local_cls, referred_cls, m2m_const[0]
- )
- backref_name = name_for_collection_relationship(
- automap_base, referred_cls, local_cls, m2m_const[1]
- )
-
- create_backref = backref_name not in referred_cfg.properties
-
- if table in table_to_map_config:
- overlaps = "__*"
- else:
- overlaps = None
-
- if relationship_name not in map_config.properties:
- if create_backref:
- backref_obj = generate_relationship(
- automap_base,
- interfaces.MANYTOMANY,
- backref,
- backref_name,
- referred_cls,
- local_cls,
- collection_class=collection_class,
- overlaps=overlaps,
- )
- else:
- backref_obj = None
-
- rel = generate_relationship(
- automap_base,
- interfaces.MANYTOMANY,
- relationship,
- relationship_name,
- local_cls,
- referred_cls,
- overlaps=overlaps,
- secondary=table,
- primaryjoin=and_(
- fk.column == fk.parent for fk in m2m_const[0].elements
- ), # type: ignore [arg-type]
- secondaryjoin=and_(
- fk.column == fk.parent for fk in m2m_const[1].elements
- ), # type: ignore [arg-type]
- backref=backref_obj,
- collection_class=collection_class,
- )
- if rel is not None:
- map_config.properties[relationship_name] = rel
-
- if not create_backref:
- referred_cfg.properties[
- backref_name
- ].back_populates = relationship_name # type: ignore[union-attr] # noqa: E501
- elif create_backref:
- rel = generate_relationship(
- automap_base,
- interfaces.MANYTOMANY,
- relationship,
- backref_name,
- referred_cls,
- local_cls,
- overlaps=overlaps,
- secondary=table,
- primaryjoin=and_(
- fk.column == fk.parent for fk in m2m_const[1].elements
- ), # type: ignore [arg-type]
- secondaryjoin=and_(
- fk.column == fk.parent for fk in m2m_const[0].elements
- ), # type: ignore [arg-type]
- back_populates=relationship_name,
- collection_class=collection_class,
- )
- if rel is not None:
- referred_cfg.properties[backref_name] = rel
- map_config.properties[
- relationship_name
- ].back_populates = backref_name # type: ignore[union-attr]