From 12cf076118570eebbff08c6b3090e0d4798447a1 Mon Sep 17 00:00:00 2001 From: cyfraeviolae Date: Wed, 3 Apr 2024 03:17:55 -0400 Subject: no venv --- .../site-packages/sqlalchemy/ext/automap.py | 1658 -------------------- 1 file changed, 1658 deletions(-) delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/ext/automap.py') 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 -# -# -# 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 `_ -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 - # "_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.``, 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.``), 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 `_ 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_"``, the event could -be applied as:: - - @event.listens_for(Base.metadata, "column_reflect") - def column_reflect(inspector, table, column_info): - # set column.key = "attr_" - 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] -- cgit v1.2.3