main commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-16 16:30:25 +09:00
parent 91c7e04474
commit 537e7b363f
1146 changed files with 45926 additions and 77196 deletions

View File

@@ -1,5 +1,5 @@
# ext/automap.py
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@@ -11,7 +11,7 @@ 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://pypi.org/project/sqlsoup/>`_
`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
@@ -64,7 +64,7 @@ asking it to reflect the schema and produce mappings::
# collection-based relationships are by default named
# "<classname>_collection"
u1 = session.query(User).first()
print(u1.address_collection)
print (u1.address_collection)
Above, calling :meth:`.AutomapBase.prepare` while passing along the
:paramref:`.AutomapBase.prepare.reflect` parameter indicates that the
@@ -101,7 +101,6 @@ 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
@@ -109,15 +108,13 @@ explicit table declaration::
# 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"])
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")),
)
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)
@@ -126,9 +123,8 @@ explicit table declaration::
Base.prepare()
# mapped classes are ready
User = Base.classes.user
Address = Base.classes.address
Order = Base.classes.user_order
User, Address, Order = Base.classes.user, Base.classes.address,\
Base.classes.user_order
.. _automap_by_module:
@@ -181,23 +177,18 @@ the schema name ``default`` is used if no schema is present::
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
)
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
@@ -260,13 +251,12 @@ established based on the table name we use. If our schema contains tables
# automap base
Base = automap_base()
# pre-declare User for the 'user' table
class User(Base):
__tablename__ = "user"
__tablename__ = 'user'
# override schema elements like Columns
user_name = Column("name", String)
user_name = Column('name', String)
# override relationships too, if desired.
# we must use the same name that automap would use for the
@@ -274,7 +264,6 @@ established based on the table name we use. If our schema contains tables
# generate for "address"
address_collection = relationship("address", collection_class=set)
# reflect
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine)
@@ -285,11 +274,11 @@ established based on the table name we use. If our schema contains tables
Address = Base.classes.address
u1 = session.query(User).first()
print(u1.address_collection)
print (u1.address_collection)
# the backref is still there:
a1 = session.query(Address).first()
print(a1.user)
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.
@@ -316,49 +305,35 @@ scheme for class names and a "pluralizer" for collection names using the
import re
import inflect
def camelize_classname(base, tablename, table):
"Produce a 'camelized' class name, e.g."
"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:],
)
)
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."
"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:]
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,
)
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
@@ -447,21 +422,16 @@ Below is an illustration of how to send
options along to all one-to-many relationships::
from sqlalchemy.ext.automap import generate_relationship
from sqlalchemy.orm import interfaces
def _gen_relationship(
base, direction, return_fn, attrname, local_cls, referred_cls, **kw
):
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
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
)
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
@@ -470,7 +440,8 @@ options along to all one-to-many relationships::
Base = automap_base()
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)
Base.prepare(autoload_with=engine,
generate_relationship=_gen_relationship)
Many-to-Many relationships
--------------------------
@@ -511,20 +482,18 @@ two classes that are in an inheritance relationship. That is, with two
classes given as follows::
class Employee(Base):
__tablename__ = "employee"
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
type = Column(String(50))
__mapper_args__ = {
"polymorphic_identity": "employee",
"polymorphic_on": type,
'polymorphic_identity':'employee', 'polymorphic_on': type
}
class Engineer(Employee):
__tablename__ = "engineer"
id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
__tablename__ = 'engineer'
id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
__mapper_args__ = {
"polymorphic_identity": "engineer",
'polymorphic_identity':'engineer',
}
The foreign key from ``Engineer`` to ``Employee`` is used not for a
@@ -539,28 +508,25 @@ we want as well as the ``inherit_condition``, as these are not things
SQLAlchemy can guess::
class Employee(Base):
__tablename__ = "employee"
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
type = Column(String(50))
__mapper_args__ = {
"polymorphic_identity": "employee",
"polymorphic_on": type,
'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"))
__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
)
favorite_employee = relationship(Employee,
foreign_keys=favorite_employee_id)
__mapper_args__ = {
"polymorphic_identity": "engineer",
"inherit_condition": id == Employee.id,
'polymorphic_identity':'engineer',
'inherit_condition': id == Employee.id
}
Handling Simple Naming Conflicts
@@ -593,24 +559,20 @@ 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
):
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)
)
"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,
)
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
@@ -619,14 +581,13 @@ to a new name::
Base = automap_base()
class TableB(Base):
__tablename__ = "table_b"
_table_a = Column("table_a", ForeignKey("table_a.id"))
__tablename__ = 'table_b'
_table_a = Column('table_a', ForeignKey('table_a.id'))
Base.prepare(autoload_with=engine)
Using Automap with Explicit Declarations
========================================
@@ -642,29 +603,26 @@ defines table metadata::
Base = automap_base()
class User(Base):
__tablename__ = "user"
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = "address"
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(ForeignKey("user.id"))
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")
a1 = Address(email='u1')
a2 = Address(email='u2')
u1 = User(address_collection=[a1, a2])
assert a1.user is u1
@@ -693,8 +651,7 @@ 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()
column_info['key'] = "attr_%s" % column_info['name'].lower()
# run reflection
Base.prepare(autoload_with=engine)
@@ -758,9 +715,8 @@ _VT = TypeVar("_VT", bound=Any)
class PythonNameForTableType(Protocol):
def __call__(
self, base: Type[Any], tablename: str, table: Table
) -> str: ...
def __call__(self, base: Type[Any], tablename: str, table: Table) -> str:
...
def classname_for_table(
@@ -807,7 +763,8 @@ class NameForScalarRelationshipType(Protocol):
local_cls: Type[Any],
referred_cls: Type[Any],
constraint: ForeignKeyConstraint,
) -> str: ...
) -> str:
...
def name_for_scalar_relationship(
@@ -847,7 +804,8 @@ class NameForCollectionRelationshipType(Protocol):
local_cls: Type[Any],
referred_cls: Type[Any],
constraint: ForeignKeyConstraint,
) -> str: ...
) -> str:
...
def name_for_collection_relationship(
@@ -892,7 +850,8 @@ class GenerateRelationshipType(Protocol):
local_cls: Type[Any],
referred_cls: Type[Any],
**kw: Any,
) -> Relationship[Any]: ...
) -> Relationship[Any]:
...
@overload
def __call__(
@@ -904,7 +863,8 @@ class GenerateRelationshipType(Protocol):
local_cls: Type[Any],
referred_cls: Type[Any],
**kw: Any,
) -> ORMBackrefArgument: ...
) -> ORMBackrefArgument:
...
def __call__(
self,
@@ -917,7 +877,8 @@ class GenerateRelationshipType(Protocol):
local_cls: Type[Any],
referred_cls: Type[Any],
**kw: Any,
) -> Union[ORMBackrefArgument, Relationship[Any]]: ...
) -> Union[ORMBackrefArgument, Relationship[Any]]:
...
@overload
@@ -929,7 +890,8 @@ def generate_relationship(
local_cls: Type[Any],
referred_cls: Type[Any],
**kw: Any,
) -> Relationship[Any]: ...
) -> Relationship[Any]:
...
@overload
@@ -941,7 +903,8 @@ def generate_relationship(
local_cls: Type[Any],
referred_cls: Type[Any],
**kw: Any,
) -> ORMBackrefArgument: ...
) -> ORMBackrefArgument:
...
def generate_relationship(
@@ -1045,12 +1008,6 @@ class AutomapBase:
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]