SQLAlchemy 2.0.36__cp313-cp313-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. SQLAlchemy-2.0.36.dist-info/LICENSE +19 -0
  2. SQLAlchemy-2.0.36.dist-info/METADATA +243 -0
  3. SQLAlchemy-2.0.36.dist-info/RECORD +273 -0
  4. SQLAlchemy-2.0.36.dist-info/WHEEL +5 -0
  5. SQLAlchemy-2.0.36.dist-info/top_level.txt +1 -0
  6. sqlalchemy/__init__.py +294 -0
  7. sqlalchemy/connectors/__init__.py +18 -0
  8. sqlalchemy/connectors/aioodbc.py +174 -0
  9. sqlalchemy/connectors/asyncio.py +213 -0
  10. sqlalchemy/connectors/pyodbc.py +249 -0
  11. sqlalchemy/cyextension/__init__.py +6 -0
  12. sqlalchemy/cyextension/collections.cp313-win32.pyd +0 -0
  13. sqlalchemy/cyextension/collections.pyx +409 -0
  14. sqlalchemy/cyextension/immutabledict.cp313-win32.pyd +0 -0
  15. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  16. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  17. sqlalchemy/cyextension/processors.cp313-win32.pyd +0 -0
  18. sqlalchemy/cyextension/processors.pyx +68 -0
  19. sqlalchemy/cyextension/resultproxy.cp313-win32.pyd +0 -0
  20. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  21. sqlalchemy/cyextension/util.cp313-win32.pyd +0 -0
  22. sqlalchemy/cyextension/util.pyx +91 -0
  23. sqlalchemy/dialects/__init__.py +61 -0
  24. sqlalchemy/dialects/_typing.py +25 -0
  25. sqlalchemy/dialects/mssql/__init__.py +88 -0
  26. sqlalchemy/dialects/mssql/aioodbc.py +64 -0
  27. sqlalchemy/dialects/mssql/base.py +4010 -0
  28. sqlalchemy/dialects/mssql/information_schema.py +254 -0
  29. sqlalchemy/dialects/mssql/json.py +133 -0
  30. sqlalchemy/dialects/mssql/provision.py +162 -0
  31. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  32. sqlalchemy/dialects/mssql/pyodbc.py +745 -0
  33. sqlalchemy/dialects/mysql/__init__.py +101 -0
  34. sqlalchemy/dialects/mysql/aiomysql.py +333 -0
  35. sqlalchemy/dialects/mysql/asyncmy.py +337 -0
  36. sqlalchemy/dialects/mysql/base.py +3494 -0
  37. sqlalchemy/dialects/mysql/cymysql.py +84 -0
  38. sqlalchemy/dialects/mysql/dml.py +219 -0
  39. sqlalchemy/dialects/mysql/enumerated.py +244 -0
  40. sqlalchemy/dialects/mysql/expression.py +141 -0
  41. sqlalchemy/dialects/mysql/json.py +81 -0
  42. sqlalchemy/dialects/mysql/mariadb.py +32 -0
  43. sqlalchemy/dialects/mysql/mariadbconnector.py +277 -0
  44. sqlalchemy/dialects/mysql/mysqlconnector.py +180 -0
  45. sqlalchemy/dialects/mysql/mysqldb.py +303 -0
  46. sqlalchemy/dialects/mysql/provision.py +110 -0
  47. sqlalchemy/dialects/mysql/pymysql.py +137 -0
  48. sqlalchemy/dialects/mysql/pyodbc.py +138 -0
  49. sqlalchemy/dialects/mysql/reflection.py +677 -0
  50. sqlalchemy/dialects/mysql/reserved_words.py +571 -0
  51. sqlalchemy/dialects/mysql/types.py +774 -0
  52. sqlalchemy/dialects/oracle/__init__.py +67 -0
  53. sqlalchemy/dialects/oracle/base.py +3271 -0
  54. sqlalchemy/dialects/oracle/cx_oracle.py +1483 -0
  55. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  56. sqlalchemy/dialects/oracle/oracledb.py +431 -0
  57. sqlalchemy/dialects/oracle/provision.py +220 -0
  58. sqlalchemy/dialects/oracle/types.py +287 -0
  59. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  60. sqlalchemy/dialects/postgresql/_psycopg_common.py +187 -0
  61. sqlalchemy/dialects/postgresql/array.py +425 -0
  62. sqlalchemy/dialects/postgresql/asyncpg.py +1274 -0
  63. sqlalchemy/dialects/postgresql/base.py +5008 -0
  64. sqlalchemy/dialects/postgresql/dml.py +310 -0
  65. sqlalchemy/dialects/postgresql/ext.py +496 -0
  66. sqlalchemy/dialects/postgresql/hstore.py +397 -0
  67. sqlalchemy/dialects/postgresql/json.py +333 -0
  68. sqlalchemy/dialects/postgresql/named_types.py +509 -0
  69. sqlalchemy/dialects/postgresql/operators.py +129 -0
  70. sqlalchemy/dialects/postgresql/pg8000.py +662 -0
  71. sqlalchemy/dialects/postgresql/pg_catalog.py +300 -0
  72. sqlalchemy/dialects/postgresql/provision.py +175 -0
  73. sqlalchemy/dialects/postgresql/psycopg.py +772 -0
  74. sqlalchemy/dialects/postgresql/psycopg2.py +886 -0
  75. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  76. sqlalchemy/dialects/postgresql/ranges.py +1029 -0
  77. sqlalchemy/dialects/postgresql/types.py +303 -0
  78. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  79. sqlalchemy/dialects/sqlite/aiosqlite.py +396 -0
  80. sqlalchemy/dialects/sqlite/base.py +2805 -0
  81. sqlalchemy/dialects/sqlite/dml.py +240 -0
  82. sqlalchemy/dialects/sqlite/json.py +92 -0
  83. sqlalchemy/dialects/sqlite/provision.py +198 -0
  84. sqlalchemy/dialects/sqlite/pysqlcipher.py +155 -0
  85. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  86. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  87. sqlalchemy/engine/__init__.py +62 -0
  88. sqlalchemy/engine/_py_processors.py +136 -0
  89. sqlalchemy/engine/_py_row.py +128 -0
  90. sqlalchemy/engine/_py_util.py +74 -0
  91. sqlalchemy/engine/base.py +3375 -0
  92. sqlalchemy/engine/characteristics.py +155 -0
  93. sqlalchemy/engine/create.py +875 -0
  94. sqlalchemy/engine/cursor.py +2181 -0
  95. sqlalchemy/engine/default.py +2365 -0
  96. sqlalchemy/engine/events.py +951 -0
  97. sqlalchemy/engine/interfaces.py +3403 -0
  98. sqlalchemy/engine/mock.py +131 -0
  99. sqlalchemy/engine/processors.py +61 -0
  100. sqlalchemy/engine/reflection.py +2098 -0
  101. sqlalchemy/engine/result.py +2382 -0
  102. sqlalchemy/engine/row.py +401 -0
  103. sqlalchemy/engine/strategies.py +19 -0
  104. sqlalchemy/engine/url.py +910 -0
  105. sqlalchemy/engine/util.py +167 -0
  106. sqlalchemy/event/__init__.py +25 -0
  107. sqlalchemy/event/api.py +225 -0
  108. sqlalchemy/event/attr.py +655 -0
  109. sqlalchemy/event/base.py +470 -0
  110. sqlalchemy/event/legacy.py +246 -0
  111. sqlalchemy/event/registry.py +386 -0
  112. sqlalchemy/events.py +17 -0
  113. sqlalchemy/exc.py +830 -0
  114. sqlalchemy/ext/__init__.py +11 -0
  115. sqlalchemy/ext/associationproxy.py +2013 -0
  116. sqlalchemy/ext/asyncio/__init__.py +25 -0
  117. sqlalchemy/ext/asyncio/base.py +279 -0
  118. sqlalchemy/ext/asyncio/engine.py +1466 -0
  119. sqlalchemy/ext/asyncio/exc.py +21 -0
  120. sqlalchemy/ext/asyncio/result.py +961 -0
  121. sqlalchemy/ext/asyncio/scoping.py +1614 -0
  122. sqlalchemy/ext/asyncio/session.py +1936 -0
  123. sqlalchemy/ext/automap.py +1691 -0
  124. sqlalchemy/ext/baked.py +574 -0
  125. sqlalchemy/ext/compiler.py +570 -0
  126. sqlalchemy/ext/declarative/__init__.py +65 -0
  127. sqlalchemy/ext/declarative/extensions.py +548 -0
  128. sqlalchemy/ext/horizontal_shard.py +481 -0
  129. sqlalchemy/ext/hybrid.py +1514 -0
  130. sqlalchemy/ext/indexable.py +341 -0
  131. sqlalchemy/ext/instrumentation.py +450 -0
  132. sqlalchemy/ext/mutable.py +1073 -0
  133. sqlalchemy/ext/mypy/__init__.py +6 -0
  134. sqlalchemy/ext/mypy/apply.py +320 -0
  135. sqlalchemy/ext/mypy/decl_class.py +515 -0
  136. sqlalchemy/ext/mypy/infer.py +590 -0
  137. sqlalchemy/ext/mypy/names.py +335 -0
  138. sqlalchemy/ext/mypy/plugin.py +303 -0
  139. sqlalchemy/ext/mypy/util.py +357 -0
  140. sqlalchemy/ext/orderinglist.py +416 -0
  141. sqlalchemy/ext/serializer.py +181 -0
  142. sqlalchemy/future/__init__.py +16 -0
  143. sqlalchemy/future/engine.py +15 -0
  144. sqlalchemy/inspection.py +174 -0
  145. sqlalchemy/log.py +288 -0
  146. sqlalchemy/orm/__init__.py +170 -0
  147. sqlalchemy/orm/_orm_constructors.py +2571 -0
  148. sqlalchemy/orm/_typing.py +179 -0
  149. sqlalchemy/orm/attributes.py +2835 -0
  150. sqlalchemy/orm/base.py +973 -0
  151. sqlalchemy/orm/bulk_persistence.py +2123 -0
  152. sqlalchemy/orm/clsregistry.py +571 -0
  153. sqlalchemy/orm/collections.py +1620 -0
  154. sqlalchemy/orm/context.py +3268 -0
  155. sqlalchemy/orm/decl_api.py +1883 -0
  156. sqlalchemy/orm/decl_base.py +2190 -0
  157. sqlalchemy/orm/dependency.py +1304 -0
  158. sqlalchemy/orm/descriptor_props.py +1076 -0
  159. sqlalchemy/orm/dynamic.py +300 -0
  160. sqlalchemy/orm/evaluator.py +379 -0
  161. sqlalchemy/orm/events.py +3261 -0
  162. sqlalchemy/orm/exc.py +228 -0
  163. sqlalchemy/orm/identity.py +302 -0
  164. sqlalchemy/orm/instrumentation.py +754 -0
  165. sqlalchemy/orm/interfaces.py +1474 -0
  166. sqlalchemy/orm/loading.py +1682 -0
  167. sqlalchemy/orm/mapped_collection.py +557 -0
  168. sqlalchemy/orm/mapper.py +4432 -0
  169. sqlalchemy/orm/path_registry.py +811 -0
  170. sqlalchemy/orm/persistence.py +1782 -0
  171. sqlalchemy/orm/properties.py +886 -0
  172. sqlalchemy/orm/query.py +3396 -0
  173. sqlalchemy/orm/relationships.py +3500 -0
  174. sqlalchemy/orm/scoping.py +2165 -0
  175. sqlalchemy/orm/session.py +5301 -0
  176. sqlalchemy/orm/state.py +1143 -0
  177. sqlalchemy/orm/state_changes.py +198 -0
  178. sqlalchemy/orm/strategies.py +3473 -0
  179. sqlalchemy/orm/strategy_options.py +2569 -0
  180. sqlalchemy/orm/sync.py +164 -0
  181. sqlalchemy/orm/unitofwork.py +796 -0
  182. sqlalchemy/orm/util.py +2424 -0
  183. sqlalchemy/orm/writeonly.py +678 -0
  184. sqlalchemy/pool/__init__.py +44 -0
  185. sqlalchemy/pool/base.py +1515 -0
  186. sqlalchemy/pool/events.py +370 -0
  187. sqlalchemy/pool/impl.py +581 -0
  188. sqlalchemy/py.typed +0 -0
  189. sqlalchemy/schema.py +70 -0
  190. sqlalchemy/sql/__init__.py +145 -0
  191. sqlalchemy/sql/_dml_constructors.py +140 -0
  192. sqlalchemy/sql/_elements_constructors.py +1850 -0
  193. sqlalchemy/sql/_orm_types.py +20 -0
  194. sqlalchemy/sql/_py_util.py +75 -0
  195. sqlalchemy/sql/_selectable_constructors.py +635 -0
  196. sqlalchemy/sql/_typing.py +460 -0
  197. sqlalchemy/sql/annotation.py +585 -0
  198. sqlalchemy/sql/base.py +2185 -0
  199. sqlalchemy/sql/cache_key.py +1057 -0
  200. sqlalchemy/sql/coercions.py +1405 -0
  201. sqlalchemy/sql/compiler.py +7818 -0
  202. sqlalchemy/sql/crud.py +1669 -0
  203. sqlalchemy/sql/ddl.py +1378 -0
  204. sqlalchemy/sql/default_comparator.py +552 -0
  205. sqlalchemy/sql/dml.py +1817 -0
  206. sqlalchemy/sql/elements.py +5499 -0
  207. sqlalchemy/sql/events.py +455 -0
  208. sqlalchemy/sql/expression.py +162 -0
  209. sqlalchemy/sql/functions.py +2055 -0
  210. sqlalchemy/sql/lambdas.py +1449 -0
  211. sqlalchemy/sql/naming.py +212 -0
  212. sqlalchemy/sql/operators.py +2579 -0
  213. sqlalchemy/sql/roles.py +323 -0
  214. sqlalchemy/sql/schema.py +6158 -0
  215. sqlalchemy/sql/selectable.py +7004 -0
  216. sqlalchemy/sql/sqltypes.py +3827 -0
  217. sqlalchemy/sql/traversals.py +1024 -0
  218. sqlalchemy/sql/type_api.py +2339 -0
  219. sqlalchemy/sql/util.py +1486 -0
  220. sqlalchemy/sql/visitors.py +1165 -0
  221. sqlalchemy/testing/__init__.py +96 -0
  222. sqlalchemy/testing/assertions.py +989 -0
  223. sqlalchemy/testing/assertsql.py +516 -0
  224. sqlalchemy/testing/asyncio.py +135 -0
  225. sqlalchemy/testing/config.py +427 -0
  226. sqlalchemy/testing/engines.py +472 -0
  227. sqlalchemy/testing/entities.py +117 -0
  228. sqlalchemy/testing/exclusions.py +435 -0
  229. sqlalchemy/testing/fixtures/__init__.py +28 -0
  230. sqlalchemy/testing/fixtures/base.py +366 -0
  231. sqlalchemy/testing/fixtures/mypy.py +312 -0
  232. sqlalchemy/testing/fixtures/orm.py +227 -0
  233. sqlalchemy/testing/fixtures/sql.py +503 -0
  234. sqlalchemy/testing/pickleable.py +155 -0
  235. sqlalchemy/testing/plugin/__init__.py +6 -0
  236. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  237. sqlalchemy/testing/plugin/plugin_base.py +779 -0
  238. sqlalchemy/testing/plugin/pytestplugin.py +868 -0
  239. sqlalchemy/testing/profiling.py +324 -0
  240. sqlalchemy/testing/provision.py +496 -0
  241. sqlalchemy/testing/requirements.py +1818 -0
  242. sqlalchemy/testing/schema.py +224 -0
  243. sqlalchemy/testing/suite/__init__.py +19 -0
  244. sqlalchemy/testing/suite/test_cte.py +211 -0
  245. sqlalchemy/testing/suite/test_ddl.py +389 -0
  246. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  247. sqlalchemy/testing/suite/test_dialect.py +740 -0
  248. sqlalchemy/testing/suite/test_insert.py +630 -0
  249. sqlalchemy/testing/suite/test_reflection.py +3225 -0
  250. sqlalchemy/testing/suite/test_results.py +502 -0
  251. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  252. sqlalchemy/testing/suite/test_select.py +1999 -0
  253. sqlalchemy/testing/suite/test_sequence.py +317 -0
  254. sqlalchemy/testing/suite/test_types.py +2141 -0
  255. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  256. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  257. sqlalchemy/testing/util.py +537 -0
  258. sqlalchemy/testing/warnings.py +52 -0
  259. sqlalchemy/types.py +76 -0
  260. sqlalchemy/util/__init__.py +160 -0
  261. sqlalchemy/util/_collections.py +715 -0
  262. sqlalchemy/util/_concurrency_py3k.py +288 -0
  263. sqlalchemy/util/_has_cy.py +40 -0
  264. sqlalchemy/util/_py_collections.py +541 -0
  265. sqlalchemy/util/compat.py +301 -0
  266. sqlalchemy/util/concurrency.py +108 -0
  267. sqlalchemy/util/deprecations.py +401 -0
  268. sqlalchemy/util/langhelpers.py +2218 -0
  269. sqlalchemy/util/preloaded.py +150 -0
  270. sqlalchemy/util/queue.py +322 -0
  271. sqlalchemy/util/tool_support.py +201 -0
  272. sqlalchemy/util/topological.py +120 -0
  273. sqlalchemy/util/typing.py +629 -0
@@ -0,0 +1,1691 @@
1
+ # ext/automap.py
2
+ # Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+
8
+ r"""Define an extension to the :mod:`sqlalchemy.ext.declarative` system
9
+ which automatically generates mapped classes and relationships from a database
10
+ schema, typically though not necessarily one which is reflected.
11
+
12
+ It is hoped that the :class:`.AutomapBase` system provides a quick
13
+ and modernized solution to the problem that the very famous
14
+ `SQLSoup <https://pypi.org/project/sqlsoup/>`_
15
+ also tries to solve, that of generating a quick and rudimentary object
16
+ model from an existing database on the fly. By addressing the issue strictly
17
+ at the mapper configuration level, and integrating fully with existing
18
+ Declarative class techniques, :class:`.AutomapBase` seeks to provide
19
+ a well-integrated approach to the issue of expediently auto-generating ad-hoc
20
+ mappings.
21
+
22
+ .. tip:: The :ref:`automap_toplevel` extension is geared towards a
23
+ "zero declaration" approach, where a complete ORM model including classes
24
+ and pre-named relationships can be generated on the fly from a database
25
+ schema. For applications that still want to use explicit class declarations
26
+ including explicit relationship definitions in conjunction with reflection
27
+ of tables, the :class:`.DeferredReflection` class, described at
28
+ :ref:`orm_declarative_reflected_deferred_reflection`, is a better choice.
29
+
30
+ .. _automap_basic_use:
31
+
32
+ Basic Use
33
+ =========
34
+
35
+ The simplest usage is to reflect an existing database into a new model.
36
+ We create a new :class:`.AutomapBase` class in a similar manner as to how
37
+ we create a declarative base class, using :func:`.automap_base`.
38
+ We then call :meth:`.AutomapBase.prepare` on the resulting base class,
39
+ asking it to reflect the schema and produce mappings::
40
+
41
+ from sqlalchemy.ext.automap import automap_base
42
+ from sqlalchemy.orm import Session
43
+ from sqlalchemy import create_engine
44
+
45
+ Base = automap_base()
46
+
47
+ # engine, suppose it has two tables 'user' and 'address' set up
48
+ engine = create_engine("sqlite:///mydatabase.db")
49
+
50
+ # reflect the tables
51
+ Base.prepare(autoload_with=engine)
52
+
53
+ # mapped classes are now created with names by default
54
+ # matching that of the table name.
55
+ User = Base.classes.user
56
+ Address = Base.classes.address
57
+
58
+ session = Session(engine)
59
+
60
+ # rudimentary relationships are produced
61
+ session.add(Address(email_address="foo@bar.com", user=User(name="foo")))
62
+ session.commit()
63
+
64
+ # collection-based relationships are by default named
65
+ # "<classname>_collection"
66
+ u1 = session.query(User).first()
67
+ print(u1.address_collection)
68
+
69
+ Above, calling :meth:`.AutomapBase.prepare` while passing along the
70
+ :paramref:`.AutomapBase.prepare.reflect` parameter indicates that the
71
+ :meth:`_schema.MetaData.reflect`
72
+ method will be called on this declarative base
73
+ classes' :class:`_schema.MetaData` collection; then, each **viable**
74
+ :class:`_schema.Table` within the :class:`_schema.MetaData`
75
+ will get a new mapped class
76
+ generated automatically. The :class:`_schema.ForeignKeyConstraint`
77
+ objects which
78
+ link the various tables together will be used to produce new, bidirectional
79
+ :func:`_orm.relationship` objects between classes.
80
+ The classes and relationships
81
+ follow along a default naming scheme that we can customize. At this point,
82
+ our basic mapping consisting of related ``User`` and ``Address`` classes is
83
+ ready to use in the traditional way.
84
+
85
+ .. note:: By **viable**, we mean that for a table to be mapped, it must
86
+ specify a primary key. Additionally, if the table is detected as being
87
+ a pure association table between two other tables, it will not be directly
88
+ mapped and will instead be configured as a many-to-many table between
89
+ the mappings for the two referring tables.
90
+
91
+ Generating Mappings from an Existing MetaData
92
+ =============================================
93
+
94
+ We can pass a pre-declared :class:`_schema.MetaData` object to
95
+ :func:`.automap_base`.
96
+ This object can be constructed in any way, including programmatically, from
97
+ a serialized file, or from itself being reflected using
98
+ :meth:`_schema.MetaData.reflect`.
99
+ Below we illustrate a combination of reflection and
100
+ explicit table declaration::
101
+
102
+ from sqlalchemy import create_engine, MetaData, Table, Column, ForeignKey
103
+ from sqlalchemy.ext.automap import automap_base
104
+
105
+ engine = create_engine("sqlite:///mydatabase.db")
106
+
107
+ # produce our own MetaData object
108
+ metadata = MetaData()
109
+
110
+ # we can reflect it ourselves from a database, using options
111
+ # such as 'only' to limit what tables we look at...
112
+ metadata.reflect(engine, only=["user", "address"])
113
+
114
+ # ... or just define our own Table objects with it (or combine both)
115
+ Table(
116
+ "user_order",
117
+ metadata,
118
+ Column("id", Integer, primary_key=True),
119
+ Column("user_id", ForeignKey("user.id")),
120
+ )
121
+
122
+ # we can then produce a set of mappings from this MetaData.
123
+ Base = automap_base(metadata=metadata)
124
+
125
+ # calling prepare() just sets up mapped classes and relationships.
126
+ Base.prepare()
127
+
128
+ # mapped classes are ready
129
+ User = Base.classes.user
130
+ Address = Base.classes.address
131
+ Order = Base.classes.user_order
132
+
133
+ .. _automap_by_module:
134
+
135
+ Generating Mappings from Multiple Schemas
136
+ =========================================
137
+
138
+ The :meth:`.AutomapBase.prepare` method when used with reflection may reflect
139
+ tables from one schema at a time at most, using the
140
+ :paramref:`.AutomapBase.prepare.schema` parameter to indicate the name of a
141
+ schema to be reflected from. In order to populate the :class:`.AutomapBase`
142
+ with tables from multiple schemas, :meth:`.AutomapBase.prepare` may be invoked
143
+ multiple times, each time passing a different name to the
144
+ :paramref:`.AutomapBase.prepare.schema` parameter. The
145
+ :meth:`.AutomapBase.prepare` method keeps an internal list of
146
+ :class:`_schema.Table` objects that have already been mapped, and will add new
147
+ mappings only for those :class:`_schema.Table` objects that are new since the
148
+ last time :meth:`.AutomapBase.prepare` was run::
149
+
150
+ e = create_engine("postgresql://scott:tiger@localhost/test")
151
+
152
+ Base.metadata.create_all(e)
153
+
154
+ Base = automap_base()
155
+
156
+ Base.prepare(e)
157
+ Base.prepare(e, schema="test_schema")
158
+ Base.prepare(e, schema="test_schema_2")
159
+
160
+ .. versionadded:: 2.0 The :meth:`.AutomapBase.prepare` method may be called
161
+ any number of times; only newly added tables will be mapped
162
+ on each run. Previously in version 1.4 and earlier, multiple calls would
163
+ cause errors as it would attempt to re-map an already mapped class.
164
+ The previous workaround approach of invoking
165
+ :meth:`_schema.MetaData.reflect` directly remains available as well.
166
+
167
+ Automapping same-named tables across multiple schemas
168
+ -----------------------------------------------------
169
+
170
+ For the common case where multiple schemas may have same-named tables and
171
+ therefore would generate same-named classes, conflicts can be resolved either
172
+ through use of the :paramref:`.AutomapBase.prepare.classname_for_table` hook to
173
+ apply different classnames on a per-schema basis, or by using the
174
+ :paramref:`.AutomapBase.prepare.modulename_for_table` hook, which allows
175
+ disambiguation of same-named classes by changing their effective ``__module__``
176
+ attribute. In the example below, this hook is used to create a ``__module__``
177
+ attribute for all classes that is of the form ``mymodule.<schemaname>``, where
178
+ the schema name ``default`` is used if no schema is present::
179
+
180
+ e = create_engine("postgresql://scott:tiger@localhost/test")
181
+
182
+ Base.metadata.create_all(e)
183
+
184
+
185
+ def module_name_for_table(cls, tablename, table):
186
+ if table.schema is not None:
187
+ return f"mymodule.{table.schema}"
188
+ else:
189
+ return f"mymodule.default"
190
+
191
+
192
+ Base = automap_base()
193
+
194
+ Base.prepare(e, modulename_for_table=module_name_for_table)
195
+ Base.prepare(e, schema="test_schema", modulename_for_table=module_name_for_table)
196
+ Base.prepare(e, schema="test_schema_2", modulename_for_table=module_name_for_table)
197
+
198
+ The same named-classes are organized into a hierarchical collection available
199
+ at :attr:`.AutomapBase.by_module`. This collection is traversed using the
200
+ dot-separated name of a particular package/module down into the desired
201
+ class name.
202
+
203
+ .. note:: When using the :paramref:`.AutomapBase.prepare.modulename_for_table`
204
+ hook to return a new ``__module__`` that is not ``None``, the class is
205
+ **not** placed into the :attr:`.AutomapBase.classes` collection; only
206
+ classes that were not given an explicit modulename are placed here, as the
207
+ collection cannot represent same-named classes individually.
208
+
209
+ In the example above, if the database contained a table named ``accounts`` in
210
+ all three of the default schema, the ``test_schema`` schema, and the
211
+ ``test_schema_2`` schema, three separate classes will be available as::
212
+
213
+ Base.by_module.mymodule.default.accounts
214
+ Base.by_module.mymodule.test_schema.accounts
215
+ Base.by_module.mymodule.test_schema_2.accounts
216
+
217
+ The default module namespace generated for all :class:`.AutomapBase` classes is
218
+ ``sqlalchemy.ext.automap``. If no
219
+ :paramref:`.AutomapBase.prepare.modulename_for_table` hook is used, the
220
+ contents of :attr:`.AutomapBase.by_module` will be entirely within the
221
+ ``sqlalchemy.ext.automap`` namespace (e.g.
222
+ ``MyBase.by_module.sqlalchemy.ext.automap.<classname>``), which would contain
223
+ the same series of classes as what would be seen in
224
+ :attr:`.AutomapBase.classes`. Therefore it's generally only necessary to use
225
+ :attr:`.AutomapBase.by_module` when explicit ``__module__`` conventions are
226
+ present.
227
+
228
+ .. versionadded: 2.0
229
+
230
+ Added the :attr:`.AutomapBase.by_module` collection, which stores
231
+ classes within a named hierarchy based on dot-separated module names,
232
+ as well as the :paramref:`.Automap.prepare.modulename_for_table` parameter
233
+ which allows for custom ``__module__`` schemes for automapped
234
+ classes.
235
+
236
+
237
+
238
+ Specifying Classes Explicitly
239
+ =============================
240
+
241
+ .. tip:: If explicit classes are expected to be prominent in an application,
242
+ consider using :class:`.DeferredReflection` instead.
243
+
244
+ The :mod:`.sqlalchemy.ext.automap` extension allows classes to be defined
245
+ explicitly, in a way similar to that of the :class:`.DeferredReflection` class.
246
+ Classes that extend from :class:`.AutomapBase` act like regular declarative
247
+ classes, but are not immediately mapped after their construction, and are
248
+ instead mapped when we call :meth:`.AutomapBase.prepare`. The
249
+ :meth:`.AutomapBase.prepare` method will make use of the classes we've
250
+ established based on the table name we use. If our schema contains tables
251
+ ``user`` and ``address``, we can define one or both of the classes to be used::
252
+
253
+ from sqlalchemy.ext.automap import automap_base
254
+ from sqlalchemy import create_engine
255
+
256
+ # automap base
257
+ Base = automap_base()
258
+
259
+
260
+ # pre-declare User for the 'user' table
261
+ class User(Base):
262
+ __tablename__ = "user"
263
+
264
+ # override schema elements like Columns
265
+ user_name = Column("name", String)
266
+
267
+ # override relationships too, if desired.
268
+ # we must use the same name that automap would use for the
269
+ # relationship, and also must refer to the class name that automap will
270
+ # generate for "address"
271
+ address_collection = relationship("address", collection_class=set)
272
+
273
+
274
+ # reflect
275
+ engine = create_engine("sqlite:///mydatabase.db")
276
+ Base.prepare(autoload_with=engine)
277
+
278
+ # we still have Address generated from the tablename "address",
279
+ # but User is the same as Base.classes.User now
280
+
281
+ Address = Base.classes.address
282
+
283
+ u1 = session.query(User).first()
284
+ print(u1.address_collection)
285
+
286
+ # the backref is still there:
287
+ a1 = session.query(Address).first()
288
+ print(a1.user)
289
+
290
+ Above, one of the more intricate details is that we illustrated overriding
291
+ one of the :func:`_orm.relationship` objects that automap would have created.
292
+ To do this, we needed to make sure the names match up with what automap
293
+ would normally generate, in that the relationship name would be
294
+ ``User.address_collection`` and the name of the class referred to, from
295
+ automap's perspective, is called ``address``, even though we are referring to
296
+ it as ``Address`` within our usage of this class.
297
+
298
+ Overriding Naming Schemes
299
+ =========================
300
+
301
+ :mod:`.sqlalchemy.ext.automap` is tasked with producing mapped classes and
302
+ relationship names based on a schema, which means it has decision points in how
303
+ these names are determined. These three decision points are provided using
304
+ functions which can be passed to the :meth:`.AutomapBase.prepare` method, and
305
+ are known as :func:`.classname_for_table`,
306
+ :func:`.name_for_scalar_relationship`,
307
+ and :func:`.name_for_collection_relationship`. Any or all of these
308
+ functions are provided as in the example below, where we use a "camel case"
309
+ scheme for class names and a "pluralizer" for collection names using the
310
+ `Inflect <https://pypi.org/project/inflect>`_ package::
311
+
312
+ import re
313
+ import inflect
314
+
315
+
316
+ def camelize_classname(base, tablename, table):
317
+ "Produce a 'camelized' class name, e.g."
318
+ "'words_and_underscores' -> 'WordsAndUnderscores'"
319
+
320
+ return str(
321
+ tablename[0].upper()
322
+ + re.sub(
323
+ r"_([a-z])",
324
+ lambda m: m.group(1).upper(),
325
+ tablename[1:],
326
+ )
327
+ )
328
+
329
+
330
+ _pluralizer = inflect.engine()
331
+
332
+
333
+ def pluralize_collection(base, local_cls, referred_cls, constraint):
334
+ "Produce an 'uncamelized', 'pluralized' class name, e.g."
335
+ "'SomeTerm' -> 'some_terms'"
336
+
337
+ referred_name = referred_cls.__name__
338
+ uncamelized = re.sub(
339
+ r"[A-Z]",
340
+ lambda m: "_%s" % m.group(0).lower(),
341
+ referred_name,
342
+ )[1:]
343
+ pluralized = _pluralizer.plural(uncamelized)
344
+ return pluralized
345
+
346
+
347
+ from sqlalchemy.ext.automap import automap_base
348
+
349
+ Base = automap_base()
350
+
351
+ engine = create_engine("sqlite:///mydatabase.db")
352
+
353
+ Base.prepare(
354
+ autoload_with=engine,
355
+ classname_for_table=camelize_classname,
356
+ name_for_collection_relationship=pluralize_collection,
357
+ )
358
+
359
+ From the above mapping, we would now have classes ``User`` and ``Address``,
360
+ where the collection from ``User`` to ``Address`` is called
361
+ ``User.addresses``::
362
+
363
+ User, Address = Base.classes.User, Base.classes.Address
364
+
365
+ u1 = User(addresses=[Address(email="foo@bar.com")])
366
+
367
+ Relationship Detection
368
+ ======================
369
+
370
+ The vast majority of what automap accomplishes is the generation of
371
+ :func:`_orm.relationship` structures based on foreign keys. The mechanism
372
+ by which this works for many-to-one and one-to-many relationships is as
373
+ follows:
374
+
375
+ 1. A given :class:`_schema.Table`, known to be mapped to a particular class,
376
+ is examined for :class:`_schema.ForeignKeyConstraint` objects.
377
+
378
+ 2. From each :class:`_schema.ForeignKeyConstraint`, the remote
379
+ :class:`_schema.Table`
380
+ object present is matched up to the class to which it is to be mapped,
381
+ if any, else it is skipped.
382
+
383
+ 3. As the :class:`_schema.ForeignKeyConstraint`
384
+ we are examining corresponds to a
385
+ reference from the immediate mapped class, the relationship will be set up
386
+ as a many-to-one referring to the referred class; a corresponding
387
+ one-to-many backref will be created on the referred class referring
388
+ to this class.
389
+
390
+ 4. If any of the columns that are part of the
391
+ :class:`_schema.ForeignKeyConstraint`
392
+ are not nullable (e.g. ``nullable=False``), a
393
+ :paramref:`_orm.relationship.cascade` keyword argument
394
+ of ``all, delete-orphan`` will be added to the keyword arguments to
395
+ be passed to the relationship or backref. If the
396
+ :class:`_schema.ForeignKeyConstraint` reports that
397
+ :paramref:`_schema.ForeignKeyConstraint.ondelete`
398
+ is set to ``CASCADE`` for a not null or ``SET NULL`` for a nullable
399
+ set of columns, the option :paramref:`_orm.relationship.passive_deletes`
400
+ flag is set to ``True`` in the set of relationship keyword arguments.
401
+ Note that not all backends support reflection of ON DELETE.
402
+
403
+ 5. The names of the relationships are determined using the
404
+ :paramref:`.AutomapBase.prepare.name_for_scalar_relationship` and
405
+ :paramref:`.AutomapBase.prepare.name_for_collection_relationship`
406
+ callable functions. It is important to note that the default relationship
407
+ naming derives the name from the **the actual class name**. If you've
408
+ given a particular class an explicit name by declaring it, or specified an
409
+ alternate class naming scheme, that's the name from which the relationship
410
+ name will be derived.
411
+
412
+ 6. The classes are inspected for an existing mapped property matching these
413
+ names. If one is detected on one side, but none on the other side,
414
+ :class:`.AutomapBase` attempts to create a relationship on the missing side,
415
+ then uses the :paramref:`_orm.relationship.back_populates`
416
+ parameter in order to
417
+ point the new relationship to the other side.
418
+
419
+ 7. In the usual case where no relationship is on either side,
420
+ :meth:`.AutomapBase.prepare` produces a :func:`_orm.relationship` on the
421
+ "many-to-one" side and matches it to the other using the
422
+ :paramref:`_orm.relationship.backref` parameter.
423
+
424
+ 8. Production of the :func:`_orm.relationship` and optionally the
425
+ :func:`.backref`
426
+ is handed off to the :paramref:`.AutomapBase.prepare.generate_relationship`
427
+ function, which can be supplied by the end-user in order to augment
428
+ the arguments passed to :func:`_orm.relationship` or :func:`.backref` or to
429
+ make use of custom implementations of these functions.
430
+
431
+ Custom Relationship Arguments
432
+ -----------------------------
433
+
434
+ The :paramref:`.AutomapBase.prepare.generate_relationship` hook can be used
435
+ to add parameters to relationships. For most cases, we can make use of the
436
+ existing :func:`.automap.generate_relationship` function to return
437
+ the object, after augmenting the given keyword dictionary with our own
438
+ arguments.
439
+
440
+ Below is an illustration of how to send
441
+ :paramref:`_orm.relationship.cascade` and
442
+ :paramref:`_orm.relationship.passive_deletes`
443
+ options along to all one-to-many relationships::
444
+
445
+ from sqlalchemy.ext.automap import generate_relationship
446
+ from sqlalchemy.orm import interfaces
447
+
448
+
449
+ def _gen_relationship(
450
+ base, direction, return_fn, attrname, local_cls, referred_cls, **kw
451
+ ):
452
+ if direction is interfaces.ONETOMANY:
453
+ kw["cascade"] = "all, delete-orphan"
454
+ kw["passive_deletes"] = True
455
+ # make use of the built-in function to actually return
456
+ # the result.
457
+ return generate_relationship(
458
+ base, direction, return_fn, attrname, local_cls, referred_cls, **kw
459
+ )
460
+
461
+
462
+ from sqlalchemy.ext.automap import automap_base
463
+ from sqlalchemy import create_engine
464
+
465
+ # automap base
466
+ Base = automap_base()
467
+
468
+ engine = create_engine("sqlite:///mydatabase.db")
469
+ Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)
470
+
471
+ Many-to-Many relationships
472
+ --------------------------
473
+
474
+ :mod:`.sqlalchemy.ext.automap` will generate many-to-many relationships, e.g.
475
+ those which contain a ``secondary`` argument. The process for producing these
476
+ is as follows:
477
+
478
+ 1. A given :class:`_schema.Table` is examined for
479
+ :class:`_schema.ForeignKeyConstraint`
480
+ objects, before any mapped class has been assigned to it.
481
+
482
+ 2. If the table contains two and exactly two
483
+ :class:`_schema.ForeignKeyConstraint`
484
+ objects, and all columns within this table are members of these two
485
+ :class:`_schema.ForeignKeyConstraint` objects, the table is assumed to be a
486
+ "secondary" table, and will **not be mapped directly**.
487
+
488
+ 3. The two (or one, for self-referential) external tables to which the
489
+ :class:`_schema.Table`
490
+ refers to are matched to the classes to which they will be
491
+ mapped, if any.
492
+
493
+ 4. If mapped classes for both sides are located, a many-to-many bi-directional
494
+ :func:`_orm.relationship` / :func:`.backref`
495
+ pair is created between the two
496
+ classes.
497
+
498
+ 5. The override logic for many-to-many works the same as that of one-to-many/
499
+ many-to-one; the :func:`.generate_relationship` function is called upon
500
+ to generate the structures and existing attributes will be maintained.
501
+
502
+ Relationships with Inheritance
503
+ ------------------------------
504
+
505
+ :mod:`.sqlalchemy.ext.automap` will not generate any relationships between
506
+ two classes that are in an inheritance relationship. That is, with two
507
+ classes given as follows::
508
+
509
+ class Employee(Base):
510
+ __tablename__ = "employee"
511
+ id = Column(Integer, primary_key=True)
512
+ type = Column(String(50))
513
+ __mapper_args__ = {
514
+ "polymorphic_identity": "employee",
515
+ "polymorphic_on": type,
516
+ }
517
+
518
+
519
+ class Engineer(Employee):
520
+ __tablename__ = "engineer"
521
+ id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
522
+ __mapper_args__ = {
523
+ "polymorphic_identity": "engineer",
524
+ }
525
+
526
+ The foreign key from ``Engineer`` to ``Employee`` is used not for a
527
+ relationship, but to establish joined inheritance between the two classes.
528
+
529
+ Note that this means automap will not generate *any* relationships
530
+ for foreign keys that link from a subclass to a superclass. If a mapping
531
+ has actual relationships from subclass to superclass as well, those
532
+ need to be explicit. Below, as we have two separate foreign keys
533
+ from ``Engineer`` to ``Employee``, we need to set up both the relationship
534
+ we want as well as the ``inherit_condition``, as these are not things
535
+ SQLAlchemy can guess::
536
+
537
+ class Employee(Base):
538
+ __tablename__ = "employee"
539
+ id = Column(Integer, primary_key=True)
540
+ type = Column(String(50))
541
+
542
+ __mapper_args__ = {
543
+ "polymorphic_identity": "employee",
544
+ "polymorphic_on": type,
545
+ }
546
+
547
+
548
+ class Engineer(Employee):
549
+ __tablename__ = "engineer"
550
+ id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
551
+ favorite_employee_id = Column(Integer, ForeignKey("employee.id"))
552
+
553
+ favorite_employee = relationship(Employee, foreign_keys=favorite_employee_id)
554
+
555
+ __mapper_args__ = {
556
+ "polymorphic_identity": "engineer",
557
+ "inherit_condition": id == Employee.id,
558
+ }
559
+
560
+ Handling Simple Naming Conflicts
561
+ --------------------------------
562
+
563
+ In the case of naming conflicts during mapping, override any of
564
+ :func:`.classname_for_table`, :func:`.name_for_scalar_relationship`,
565
+ and :func:`.name_for_collection_relationship` as needed. For example, if
566
+ automap is attempting to name a many-to-one relationship the same as an
567
+ existing column, an alternate convention can be conditionally selected. Given
568
+ a schema:
569
+
570
+ .. sourcecode:: sql
571
+
572
+ CREATE TABLE table_a (
573
+ id INTEGER PRIMARY KEY
574
+ );
575
+
576
+ CREATE TABLE table_b (
577
+ id INTEGER PRIMARY KEY,
578
+ table_a INTEGER,
579
+ FOREIGN KEY(table_a) REFERENCES table_a(id)
580
+ );
581
+
582
+ The above schema will first automap the ``table_a`` table as a class named
583
+ ``table_a``; it will then automap a relationship onto the class for ``table_b``
584
+ with the same name as this related class, e.g. ``table_a``. This
585
+ relationship name conflicts with the mapping column ``table_b.table_a``,
586
+ and will emit an error on mapping.
587
+
588
+ We can resolve this conflict by using an underscore as follows::
589
+
590
+ def name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
591
+ name = referred_cls.__name__.lower()
592
+ local_table = local_cls.__table__
593
+ if name in local_table.columns:
594
+ newname = name + "_"
595
+ warnings.warn("Already detected name %s present. using %s" % (name, newname))
596
+ return newname
597
+ return name
598
+
599
+
600
+ Base.prepare(
601
+ autoload_with=engine,
602
+ name_for_scalar_relationship=name_for_scalar_relationship,
603
+ )
604
+
605
+ Alternatively, we can change the name on the column side. The columns
606
+ that are mapped can be modified using the technique described at
607
+ :ref:`mapper_column_distinct_names`, by assigning the column explicitly
608
+ to a new name::
609
+
610
+ Base = automap_base()
611
+
612
+
613
+ class TableB(Base):
614
+ __tablename__ = "table_b"
615
+ _table_a = Column("table_a", ForeignKey("table_a.id"))
616
+
617
+
618
+ Base.prepare(autoload_with=engine)
619
+
620
+ Using Automap with Explicit Declarations
621
+ ========================================
622
+
623
+ As noted previously, automap has no dependency on reflection, and can make
624
+ use of any collection of :class:`_schema.Table` objects within a
625
+ :class:`_schema.MetaData`
626
+ collection. From this, it follows that automap can also be used
627
+ generate missing relationships given an otherwise complete model that fully
628
+ defines table metadata::
629
+
630
+ from sqlalchemy.ext.automap import automap_base
631
+ from sqlalchemy import Column, Integer, String, ForeignKey
632
+
633
+ Base = automap_base()
634
+
635
+
636
+ class User(Base):
637
+ __tablename__ = "user"
638
+
639
+ id = Column(Integer, primary_key=True)
640
+ name = Column(String)
641
+
642
+
643
+ class Address(Base):
644
+ __tablename__ = "address"
645
+
646
+ id = Column(Integer, primary_key=True)
647
+ email = Column(String)
648
+ user_id = Column(ForeignKey("user.id"))
649
+
650
+
651
+ # produce relationships
652
+ Base.prepare()
653
+
654
+ # mapping is complete, with "address_collection" and
655
+ # "user" relationships
656
+ a1 = Address(email="u1")
657
+ a2 = Address(email="u2")
658
+ u1 = User(address_collection=[a1, a2])
659
+ assert a1.user is u1
660
+
661
+ Above, given mostly complete ``User`` and ``Address`` mappings, the
662
+ :class:`_schema.ForeignKey` which we defined on ``Address.user_id`` allowed a
663
+ bidirectional relationship pair ``Address.user`` and
664
+ ``User.address_collection`` to be generated on the mapped classes.
665
+
666
+ Note that when subclassing :class:`.AutomapBase`,
667
+ the :meth:`.AutomapBase.prepare` method is required; if not called, the classes
668
+ we've declared are in an un-mapped state.
669
+
670
+
671
+ .. _automap_intercepting_columns:
672
+
673
+ Intercepting Column Definitions
674
+ ===============================
675
+
676
+ The :class:`_schema.MetaData` and :class:`_schema.Table` objects support an
677
+ event hook :meth:`_events.DDLEvents.column_reflect` that may be used to intercept
678
+ the information reflected about a database column before the :class:`_schema.Column`
679
+ object is constructed. For example if we wanted to map columns using a
680
+ naming convention such as ``"attr_<columnname>"``, the event could
681
+ be applied as::
682
+
683
+ @event.listens_for(Base.metadata, "column_reflect")
684
+ def column_reflect(inspector, table, column_info):
685
+ # set column.key = "attr_<lower_case_name>"
686
+ column_info["key"] = "attr_%s" % column_info["name"].lower()
687
+
688
+
689
+ # run reflection
690
+ Base.prepare(autoload_with=engine)
691
+
692
+ .. versionadded:: 1.4.0b2 the :meth:`_events.DDLEvents.column_reflect` event
693
+ may be applied to a :class:`_schema.MetaData` object.
694
+
695
+ .. seealso::
696
+
697
+ :meth:`_events.DDLEvents.column_reflect`
698
+
699
+ :ref:`mapper_automated_reflection_schemes` - in the ORM mapping documentation
700
+
701
+
702
+ """ # noqa
703
+ from __future__ import annotations
704
+
705
+ import dataclasses
706
+ from typing import Any
707
+ from typing import Callable
708
+ from typing import cast
709
+ from typing import ClassVar
710
+ from typing import Dict
711
+ from typing import List
712
+ from typing import NoReturn
713
+ from typing import Optional
714
+ from typing import overload
715
+ from typing import Set
716
+ from typing import Tuple
717
+ from typing import Type
718
+ from typing import TYPE_CHECKING
719
+ from typing import TypeVar
720
+ from typing import Union
721
+
722
+ from .. import util
723
+ from ..orm import backref
724
+ from ..orm import declarative_base as _declarative_base
725
+ from ..orm import exc as orm_exc
726
+ from ..orm import interfaces
727
+ from ..orm import relationship
728
+ from ..orm.decl_base import _DeferredMapperConfig
729
+ from ..orm.mapper import _CONFIGURE_MUTEX
730
+ from ..schema import ForeignKeyConstraint
731
+ from ..sql import and_
732
+ from ..util import Properties
733
+ from ..util.typing import Protocol
734
+
735
+ if TYPE_CHECKING:
736
+ from ..engine.base import Engine
737
+ from ..orm.base import RelationshipDirection
738
+ from ..orm.relationships import ORMBackrefArgument
739
+ from ..orm.relationships import Relationship
740
+ from ..sql.schema import Column
741
+ from ..sql.schema import MetaData
742
+ from ..sql.schema import Table
743
+ from ..util import immutabledict
744
+
745
+
746
+ _KT = TypeVar("_KT", bound=Any)
747
+ _VT = TypeVar("_VT", bound=Any)
748
+
749
+
750
+ class PythonNameForTableType(Protocol):
751
+ def __call__(
752
+ self, base: Type[Any], tablename: str, table: Table
753
+ ) -> str: ...
754
+
755
+
756
+ def classname_for_table(
757
+ base: Type[Any],
758
+ tablename: str,
759
+ table: Table,
760
+ ) -> str:
761
+ """Return the class name that should be used, given the name
762
+ of a table.
763
+
764
+ The default implementation is::
765
+
766
+ return str(tablename)
767
+
768
+ Alternate implementations can be specified using the
769
+ :paramref:`.AutomapBase.prepare.classname_for_table`
770
+ parameter.
771
+
772
+ :param base: the :class:`.AutomapBase` class doing the prepare.
773
+
774
+ :param tablename: string name of the :class:`_schema.Table`.
775
+
776
+ :param table: the :class:`_schema.Table` object itself.
777
+
778
+ :return: a string class name.
779
+
780
+ .. note::
781
+
782
+ In Python 2, the string used for the class name **must** be a
783
+ non-Unicode object, e.g. a ``str()`` object. The ``.name`` attribute
784
+ of :class:`_schema.Table` is typically a Python unicode subclass,
785
+ so the
786
+ ``str()`` function should be applied to this name, after accounting for
787
+ any non-ASCII characters.
788
+
789
+ """
790
+ return str(tablename)
791
+
792
+
793
+ class NameForScalarRelationshipType(Protocol):
794
+ def __call__(
795
+ self,
796
+ base: Type[Any],
797
+ local_cls: Type[Any],
798
+ referred_cls: Type[Any],
799
+ constraint: ForeignKeyConstraint,
800
+ ) -> str: ...
801
+
802
+
803
+ def name_for_scalar_relationship(
804
+ base: Type[Any],
805
+ local_cls: Type[Any],
806
+ referred_cls: Type[Any],
807
+ constraint: ForeignKeyConstraint,
808
+ ) -> str:
809
+ """Return the attribute name that should be used to refer from one
810
+ class to another, for a scalar object reference.
811
+
812
+ The default implementation is::
813
+
814
+ return referred_cls.__name__.lower()
815
+
816
+ Alternate implementations can be specified using the
817
+ :paramref:`.AutomapBase.prepare.name_for_scalar_relationship`
818
+ parameter.
819
+
820
+ :param base: the :class:`.AutomapBase` class doing the prepare.
821
+
822
+ :param local_cls: the class to be mapped on the local side.
823
+
824
+ :param referred_cls: the class to be mapped on the referring side.
825
+
826
+ :param constraint: the :class:`_schema.ForeignKeyConstraint` that is being
827
+ inspected to produce this relationship.
828
+
829
+ """
830
+ return referred_cls.__name__.lower()
831
+
832
+
833
+ class NameForCollectionRelationshipType(Protocol):
834
+ def __call__(
835
+ self,
836
+ base: Type[Any],
837
+ local_cls: Type[Any],
838
+ referred_cls: Type[Any],
839
+ constraint: ForeignKeyConstraint,
840
+ ) -> str: ...
841
+
842
+
843
+ def name_for_collection_relationship(
844
+ base: Type[Any],
845
+ local_cls: Type[Any],
846
+ referred_cls: Type[Any],
847
+ constraint: ForeignKeyConstraint,
848
+ ) -> str:
849
+ """Return the attribute name that should be used to refer from one
850
+ class to another, for a collection reference.
851
+
852
+ The default implementation is::
853
+
854
+ return referred_cls.__name__.lower() + "_collection"
855
+
856
+ Alternate implementations
857
+ can be specified using the
858
+ :paramref:`.AutomapBase.prepare.name_for_collection_relationship`
859
+ parameter.
860
+
861
+ :param base: the :class:`.AutomapBase` class doing the prepare.
862
+
863
+ :param local_cls: the class to be mapped on the local side.
864
+
865
+ :param referred_cls: the class to be mapped on the referring side.
866
+
867
+ :param constraint: the :class:`_schema.ForeignKeyConstraint` that is being
868
+ inspected to produce this relationship.
869
+
870
+ """
871
+ return referred_cls.__name__.lower() + "_collection"
872
+
873
+
874
+ class GenerateRelationshipType(Protocol):
875
+ @overload
876
+ def __call__(
877
+ self,
878
+ base: Type[Any],
879
+ direction: RelationshipDirection,
880
+ return_fn: Callable[..., Relationship[Any]],
881
+ attrname: str,
882
+ local_cls: Type[Any],
883
+ referred_cls: Type[Any],
884
+ **kw: Any,
885
+ ) -> Relationship[Any]: ...
886
+
887
+ @overload
888
+ def __call__(
889
+ self,
890
+ base: Type[Any],
891
+ direction: RelationshipDirection,
892
+ return_fn: Callable[..., ORMBackrefArgument],
893
+ attrname: str,
894
+ local_cls: Type[Any],
895
+ referred_cls: Type[Any],
896
+ **kw: Any,
897
+ ) -> ORMBackrefArgument: ...
898
+
899
+ def __call__(
900
+ self,
901
+ base: Type[Any],
902
+ direction: RelationshipDirection,
903
+ return_fn: Union[
904
+ Callable[..., Relationship[Any]], Callable[..., ORMBackrefArgument]
905
+ ],
906
+ attrname: str,
907
+ local_cls: Type[Any],
908
+ referred_cls: Type[Any],
909
+ **kw: Any,
910
+ ) -> Union[ORMBackrefArgument, Relationship[Any]]: ...
911
+
912
+
913
+ @overload
914
+ def generate_relationship(
915
+ base: Type[Any],
916
+ direction: RelationshipDirection,
917
+ return_fn: Callable[..., Relationship[Any]],
918
+ attrname: str,
919
+ local_cls: Type[Any],
920
+ referred_cls: Type[Any],
921
+ **kw: Any,
922
+ ) -> Relationship[Any]: ...
923
+
924
+
925
+ @overload
926
+ def generate_relationship(
927
+ base: Type[Any],
928
+ direction: RelationshipDirection,
929
+ return_fn: Callable[..., ORMBackrefArgument],
930
+ attrname: str,
931
+ local_cls: Type[Any],
932
+ referred_cls: Type[Any],
933
+ **kw: Any,
934
+ ) -> ORMBackrefArgument: ...
935
+
936
+
937
+ def generate_relationship(
938
+ base: Type[Any],
939
+ direction: RelationshipDirection,
940
+ return_fn: Union[
941
+ Callable[..., Relationship[Any]], Callable[..., ORMBackrefArgument]
942
+ ],
943
+ attrname: str,
944
+ local_cls: Type[Any],
945
+ referred_cls: Type[Any],
946
+ **kw: Any,
947
+ ) -> Union[Relationship[Any], ORMBackrefArgument]:
948
+ r"""Generate a :func:`_orm.relationship` or :func:`.backref`
949
+ on behalf of two
950
+ mapped classes.
951
+
952
+ An alternate implementation of this function can be specified using the
953
+ :paramref:`.AutomapBase.prepare.generate_relationship` parameter.
954
+
955
+ The default implementation of this function is as follows::
956
+
957
+ if return_fn is backref:
958
+ return return_fn(attrname, **kw)
959
+ elif return_fn is relationship:
960
+ return return_fn(referred_cls, **kw)
961
+ else:
962
+ raise TypeError("Unknown relationship function: %s" % return_fn)
963
+
964
+ :param base: the :class:`.AutomapBase` class doing the prepare.
965
+
966
+ :param direction: indicate the "direction" of the relationship; this will
967
+ be one of :data:`.ONETOMANY`, :data:`.MANYTOONE`, :data:`.MANYTOMANY`.
968
+
969
+ :param return_fn: the function that is used by default to create the
970
+ relationship. This will be either :func:`_orm.relationship` or
971
+ :func:`.backref`. The :func:`.backref` function's result will be used to
972
+ produce a new :func:`_orm.relationship` in a second step,
973
+ so it is critical
974
+ that user-defined implementations correctly differentiate between the two
975
+ functions, if a custom relationship function is being used.
976
+
977
+ :param attrname: the attribute name to which this relationship is being
978
+ assigned. If the value of :paramref:`.generate_relationship.return_fn` is
979
+ the :func:`.backref` function, then this name is the name that is being
980
+ assigned to the backref.
981
+
982
+ :param local_cls: the "local" class to which this relationship or backref
983
+ will be locally present.
984
+
985
+ :param referred_cls: the "referred" class to which the relationship or
986
+ backref refers to.
987
+
988
+ :param \**kw: all additional keyword arguments are passed along to the
989
+ function.
990
+
991
+ :return: a :func:`_orm.relationship` or :func:`.backref` construct,
992
+ as dictated
993
+ by the :paramref:`.generate_relationship.return_fn` parameter.
994
+
995
+ """
996
+
997
+ if return_fn is backref:
998
+ return return_fn(attrname, **kw)
999
+ elif return_fn is relationship:
1000
+ return return_fn(referred_cls, **kw)
1001
+ else:
1002
+ raise TypeError("Unknown relationship function: %s" % return_fn)
1003
+
1004
+
1005
+ ByModuleProperties = Properties[Union["ByModuleProperties", Type[Any]]]
1006
+
1007
+
1008
+ class AutomapBase:
1009
+ """Base class for an "automap" schema.
1010
+
1011
+ The :class:`.AutomapBase` class can be compared to the "declarative base"
1012
+ class that is produced by the :func:`.declarative.declarative_base`
1013
+ function. In practice, the :class:`.AutomapBase` class is always used
1014
+ as a mixin along with an actual declarative base.
1015
+
1016
+ A new subclassable :class:`.AutomapBase` is typically instantiated
1017
+ using the :func:`.automap_base` function.
1018
+
1019
+ .. seealso::
1020
+
1021
+ :ref:`automap_toplevel`
1022
+
1023
+ """
1024
+
1025
+ __abstract__ = True
1026
+
1027
+ classes: ClassVar[Properties[Type[Any]]]
1028
+ """An instance of :class:`.util.Properties` containing classes.
1029
+
1030
+ This object behaves much like the ``.c`` collection on a table. Classes
1031
+ are present under the name they were given, e.g.::
1032
+
1033
+ Base = automap_base()
1034
+ Base.prepare(autoload_with=some_engine)
1035
+
1036
+ User, Address = Base.classes.User, Base.classes.Address
1037
+
1038
+ For class names that overlap with a method name of
1039
+ :class:`.util.Properties`, such as ``items()``, the getitem form
1040
+ is also supported::
1041
+
1042
+ Item = Base.classes["items"]
1043
+
1044
+ """
1045
+
1046
+ by_module: ClassVar[ByModuleProperties]
1047
+ """An instance of :class:`.util.Properties` containing a hierarchal
1048
+ structure of dot-separated module names linked to classes.
1049
+
1050
+ This collection is an alternative to the :attr:`.AutomapBase.classes`
1051
+ collection that is useful when making use of the
1052
+ :paramref:`.AutomapBase.prepare.modulename_for_table` parameter, which will
1053
+ apply distinct ``__module__`` attributes to generated classes.
1054
+
1055
+ The default ``__module__`` an automap-generated class is
1056
+ ``sqlalchemy.ext.automap``; to access this namespace using
1057
+ :attr:`.AutomapBase.by_module` looks like::
1058
+
1059
+ User = Base.by_module.sqlalchemy.ext.automap.User
1060
+
1061
+ If a class had a ``__module__`` of ``mymodule.account``, accessing
1062
+ this namespace looks like::
1063
+
1064
+ MyClass = Base.by_module.mymodule.account.MyClass
1065
+
1066
+ .. versionadded:: 2.0
1067
+
1068
+ .. seealso::
1069
+
1070
+ :ref:`automap_by_module`
1071
+
1072
+ """
1073
+
1074
+ metadata: ClassVar[MetaData]
1075
+ """Refers to the :class:`_schema.MetaData` collection that will be used
1076
+ for new :class:`_schema.Table` objects.
1077
+
1078
+ .. seealso::
1079
+
1080
+ :ref:`orm_declarative_metadata`
1081
+
1082
+ """
1083
+
1084
+ _sa_automapbase_bookkeeping: ClassVar[_Bookkeeping]
1085
+
1086
+ @classmethod
1087
+ @util.deprecated_params(
1088
+ engine=(
1089
+ "2.0",
1090
+ "The :paramref:`_automap.AutomapBase.prepare.engine` parameter "
1091
+ "is deprecated and will be removed in a future release. "
1092
+ "Please use the "
1093
+ ":paramref:`_automap.AutomapBase.prepare.autoload_with` "
1094
+ "parameter.",
1095
+ ),
1096
+ reflect=(
1097
+ "2.0",
1098
+ "The :paramref:`_automap.AutomapBase.prepare.reflect` "
1099
+ "parameter is deprecated and will be removed in a future "
1100
+ "release. Reflection is enabled when "
1101
+ ":paramref:`_automap.AutomapBase.prepare.autoload_with` "
1102
+ "is passed.",
1103
+ ),
1104
+ )
1105
+ def prepare(
1106
+ cls: Type[AutomapBase],
1107
+ autoload_with: Optional[Engine] = None,
1108
+ engine: Optional[Any] = None,
1109
+ reflect: bool = False,
1110
+ schema: Optional[str] = None,
1111
+ classname_for_table: Optional[PythonNameForTableType] = None,
1112
+ modulename_for_table: Optional[PythonNameForTableType] = None,
1113
+ collection_class: Optional[Any] = None,
1114
+ name_for_scalar_relationship: Optional[
1115
+ NameForScalarRelationshipType
1116
+ ] = None,
1117
+ name_for_collection_relationship: Optional[
1118
+ NameForCollectionRelationshipType
1119
+ ] = None,
1120
+ generate_relationship: Optional[GenerateRelationshipType] = None,
1121
+ reflection_options: Union[
1122
+ Dict[_KT, _VT], immutabledict[_KT, _VT]
1123
+ ] = util.EMPTY_DICT,
1124
+ ) -> None:
1125
+ """Extract mapped classes and relationships from the
1126
+ :class:`_schema.MetaData` and perform mappings.
1127
+
1128
+ For full documentation and examples see
1129
+ :ref:`automap_basic_use`.
1130
+
1131
+ :param autoload_with: an :class:`_engine.Engine` or
1132
+ :class:`_engine.Connection` with which
1133
+ to perform schema reflection; when specified, the
1134
+ :meth:`_schema.MetaData.reflect` method will be invoked within
1135
+ the scope of this method.
1136
+
1137
+ :param engine: legacy; use :paramref:`.AutomapBase.autoload_with`.
1138
+ Used to indicate the :class:`_engine.Engine` or
1139
+ :class:`_engine.Connection` with which to reflect tables with,
1140
+ if :paramref:`.AutomapBase.reflect` is True.
1141
+
1142
+ :param reflect: legacy; use :paramref:`.AutomapBase.autoload_with`.
1143
+ Indicates that :meth:`_schema.MetaData.reflect` should be invoked.
1144
+
1145
+ :param classname_for_table: callable function which will be used to
1146
+ produce new class names, given a table name. Defaults to
1147
+ :func:`.classname_for_table`.
1148
+
1149
+ :param modulename_for_table: callable function which will be used to
1150
+ produce the effective ``__module__`` for an internally generated
1151
+ class, to allow for multiple classes of the same name in a single
1152
+ automap base which would be in different "modules".
1153
+
1154
+ Defaults to ``None``, which will indicate that ``__module__`` will not
1155
+ be set explicitly; the Python runtime will use the value
1156
+ ``sqlalchemy.ext.automap`` for these classes.
1157
+
1158
+ When assigning ``__module__`` to generated classes, they can be
1159
+ accessed based on dot-separated module names using the
1160
+ :attr:`.AutomapBase.by_module` collection. Classes that have
1161
+ an explicit ``__module_`` assigned using this hook do **not** get
1162
+ placed into the :attr:`.AutomapBase.classes` collection, only
1163
+ into :attr:`.AutomapBase.by_module`.
1164
+
1165
+ .. versionadded:: 2.0
1166
+
1167
+ .. seealso::
1168
+
1169
+ :ref:`automap_by_module`
1170
+
1171
+ :param name_for_scalar_relationship: callable function which will be
1172
+ used to produce relationship names for scalar relationships. Defaults
1173
+ to :func:`.name_for_scalar_relationship`.
1174
+
1175
+ :param name_for_collection_relationship: callable function which will
1176
+ be used to produce relationship names for collection-oriented
1177
+ relationships. Defaults to :func:`.name_for_collection_relationship`.
1178
+
1179
+ :param generate_relationship: callable function which will be used to
1180
+ actually generate :func:`_orm.relationship` and :func:`.backref`
1181
+ constructs. Defaults to :func:`.generate_relationship`.
1182
+
1183
+ :param collection_class: the Python collection class that will be used
1184
+ when a new :func:`_orm.relationship`
1185
+ object is created that represents a
1186
+ collection. Defaults to ``list``.
1187
+
1188
+ :param schema: Schema name to reflect when reflecting tables using
1189
+ the :paramref:`.AutomapBase.prepare.autoload_with` parameter. The name
1190
+ is passed to the :paramref:`_schema.MetaData.reflect.schema` parameter
1191
+ of :meth:`_schema.MetaData.reflect`. When omitted, the default schema
1192
+ in use by the database connection is used.
1193
+
1194
+ .. note:: The :paramref:`.AutomapBase.prepare.schema`
1195
+ parameter supports reflection of a single schema at a time.
1196
+ In order to include tables from many schemas, use
1197
+ multiple calls to :meth:`.AutomapBase.prepare`.
1198
+
1199
+ For an overview of multiple-schema automap including the use
1200
+ of additional naming conventions to resolve table name
1201
+ conflicts, see the section :ref:`automap_by_module`.
1202
+
1203
+ .. versionadded:: 2.0 :meth:`.AutomapBase.prepare` supports being
1204
+ directly invoked any number of times, keeping track of tables
1205
+ that have already been processed to avoid processing them
1206
+ a second time.
1207
+
1208
+ :param reflection_options: When present, this dictionary of options
1209
+ will be passed to :meth:`_schema.MetaData.reflect`
1210
+ to supply general reflection-specific options like ``only`` and/or
1211
+ dialect-specific options like ``oracle_resolve_synonyms``.
1212
+
1213
+ .. versionadded:: 1.4
1214
+
1215
+ """
1216
+
1217
+ for mr in cls.__mro__:
1218
+ if "_sa_automapbase_bookkeeping" in mr.__dict__:
1219
+ automap_base = cast("Type[AutomapBase]", mr)
1220
+ break
1221
+ else:
1222
+ assert False, "Can't locate automap base in class hierarchy"
1223
+
1224
+ glbls = globals()
1225
+ if classname_for_table is None:
1226
+ classname_for_table = glbls["classname_for_table"]
1227
+ if name_for_scalar_relationship is None:
1228
+ name_for_scalar_relationship = glbls[
1229
+ "name_for_scalar_relationship"
1230
+ ]
1231
+ if name_for_collection_relationship is None:
1232
+ name_for_collection_relationship = glbls[
1233
+ "name_for_collection_relationship"
1234
+ ]
1235
+ if generate_relationship is None:
1236
+ generate_relationship = glbls["generate_relationship"]
1237
+ if collection_class is None:
1238
+ collection_class = list
1239
+
1240
+ if autoload_with:
1241
+ reflect = True
1242
+
1243
+ if engine:
1244
+ autoload_with = engine
1245
+
1246
+ if reflect:
1247
+ assert autoload_with
1248
+ opts = dict(
1249
+ schema=schema,
1250
+ extend_existing=True,
1251
+ autoload_replace=False,
1252
+ )
1253
+ if reflection_options:
1254
+ opts.update(reflection_options)
1255
+ cls.metadata.reflect(autoload_with, **opts) # type: ignore[arg-type] # noqa: E501
1256
+
1257
+ with _CONFIGURE_MUTEX:
1258
+ table_to_map_config: Union[
1259
+ Dict[Optional[Table], _DeferredMapperConfig],
1260
+ Dict[Table, _DeferredMapperConfig],
1261
+ ] = {
1262
+ cast("Table", m.local_table): m
1263
+ for m in _DeferredMapperConfig.classes_for_base(
1264
+ cls, sort=False
1265
+ )
1266
+ }
1267
+
1268
+ many_to_many: List[
1269
+ Tuple[Table, Table, List[ForeignKeyConstraint], Table]
1270
+ ]
1271
+ many_to_many = []
1272
+
1273
+ bookkeeping = automap_base._sa_automapbase_bookkeeping
1274
+ metadata_tables = cls.metadata.tables
1275
+
1276
+ for table_key in set(metadata_tables).difference(
1277
+ bookkeeping.table_keys
1278
+ ):
1279
+ table = metadata_tables[table_key]
1280
+ bookkeeping.table_keys.add(table_key)
1281
+
1282
+ lcl_m2m, rem_m2m, m2m_const = _is_many_to_many(cls, table)
1283
+ if lcl_m2m is not None:
1284
+ assert rem_m2m is not None
1285
+ assert m2m_const is not None
1286
+ many_to_many.append((lcl_m2m, rem_m2m, m2m_const, table))
1287
+ elif not table.primary_key:
1288
+ continue
1289
+ elif table not in table_to_map_config:
1290
+ clsdict: Dict[str, Any] = {"__table__": table}
1291
+ if modulename_for_table is not None:
1292
+ new_module = modulename_for_table(
1293
+ cls, table.name, table
1294
+ )
1295
+ if new_module is not None:
1296
+ clsdict["__module__"] = new_module
1297
+ else:
1298
+ new_module = None
1299
+
1300
+ newname = classname_for_table(cls, table.name, table)
1301
+ if new_module is None and newname in cls.classes:
1302
+ util.warn(
1303
+ "Ignoring duplicate class name "
1304
+ f"'{newname}' "
1305
+ "received in automap base for table "
1306
+ f"{table.key} without "
1307
+ "``__module__`` being set; consider using the "
1308
+ "``modulename_for_table`` hook"
1309
+ )
1310
+ continue
1311
+
1312
+ mapped_cls = type(
1313
+ newname,
1314
+ (automap_base,),
1315
+ clsdict,
1316
+ )
1317
+ map_config = _DeferredMapperConfig.config_for_cls(
1318
+ mapped_cls
1319
+ )
1320
+ assert map_config.cls.__name__ == newname
1321
+ if new_module is None:
1322
+ cls.classes[newname] = mapped_cls
1323
+
1324
+ by_module_properties: ByModuleProperties = cls.by_module
1325
+ for token in map_config.cls.__module__.split("."):
1326
+ if token not in by_module_properties:
1327
+ by_module_properties[token] = util.Properties({})
1328
+
1329
+ props = by_module_properties[token]
1330
+
1331
+ # we can assert this because the clsregistry
1332
+ # module would have raised if there was a mismatch
1333
+ # between modules/classes already.
1334
+ # see test_cls_schema_name_conflict
1335
+ assert isinstance(props, Properties)
1336
+ by_module_properties = props
1337
+
1338
+ by_module_properties[map_config.cls.__name__] = mapped_cls
1339
+
1340
+ table_to_map_config[table] = map_config
1341
+
1342
+ for map_config in table_to_map_config.values():
1343
+ _relationships_for_fks(
1344
+ automap_base,
1345
+ map_config,
1346
+ table_to_map_config,
1347
+ collection_class,
1348
+ name_for_scalar_relationship,
1349
+ name_for_collection_relationship,
1350
+ generate_relationship,
1351
+ )
1352
+
1353
+ for lcl_m2m, rem_m2m, m2m_const, table in many_to_many:
1354
+ _m2m_relationship(
1355
+ automap_base,
1356
+ lcl_m2m,
1357
+ rem_m2m,
1358
+ m2m_const,
1359
+ table,
1360
+ table_to_map_config,
1361
+ collection_class,
1362
+ name_for_scalar_relationship,
1363
+ name_for_collection_relationship,
1364
+ generate_relationship,
1365
+ )
1366
+
1367
+ for map_config in _DeferredMapperConfig.classes_for_base(
1368
+ automap_base
1369
+ ):
1370
+ map_config.map()
1371
+
1372
+ _sa_decl_prepare = True
1373
+ """Indicate that the mapping of classes should be deferred.
1374
+
1375
+ The presence of this attribute name indicates to declarative
1376
+ that the call to mapper() should not occur immediately; instead,
1377
+ information about the table and attributes to be mapped are gathered
1378
+ into an internal structure called _DeferredMapperConfig. These
1379
+ objects can be collected later using classes_for_base(), additional
1380
+ mapping decisions can be made, and then the map() method will actually
1381
+ apply the mapping.
1382
+
1383
+ The only real reason this deferral of the whole
1384
+ thing is needed is to support primary key columns that aren't reflected
1385
+ yet when the class is declared; everything else can theoretically be
1386
+ added to the mapper later. However, the _DeferredMapperConfig is a
1387
+ nice interface in any case which exists at that not usually exposed point
1388
+ at which declarative has the class and the Table but hasn't called
1389
+ mapper() yet.
1390
+
1391
+ """
1392
+
1393
+ @classmethod
1394
+ def _sa_raise_deferred_config(cls) -> NoReturn:
1395
+ raise orm_exc.UnmappedClassError(
1396
+ cls,
1397
+ msg="Class %s is a subclass of AutomapBase. "
1398
+ "Mappings are not produced until the .prepare() "
1399
+ "method is called on the class hierarchy."
1400
+ % orm_exc._safe_cls_name(cls),
1401
+ )
1402
+
1403
+
1404
+ @dataclasses.dataclass
1405
+ class _Bookkeeping:
1406
+ __slots__ = ("table_keys",)
1407
+
1408
+ table_keys: Set[str]
1409
+
1410
+
1411
+ def automap_base(
1412
+ declarative_base: Optional[Type[Any]] = None, **kw: Any
1413
+ ) -> Any:
1414
+ r"""Produce a declarative automap base.
1415
+
1416
+ This function produces a new base class that is a product of the
1417
+ :class:`.AutomapBase` class as well a declarative base produced by
1418
+ :func:`.declarative.declarative_base`.
1419
+
1420
+ All parameters other than ``declarative_base`` are keyword arguments
1421
+ that are passed directly to the :func:`.declarative.declarative_base`
1422
+ function.
1423
+
1424
+ :param declarative_base: an existing class produced by
1425
+ :func:`.declarative.declarative_base`. When this is passed, the function
1426
+ no longer invokes :func:`.declarative.declarative_base` itself, and all
1427
+ other keyword arguments are ignored.
1428
+
1429
+ :param \**kw: keyword arguments are passed along to
1430
+ :func:`.declarative.declarative_base`.
1431
+
1432
+ """
1433
+ if declarative_base is None:
1434
+ Base = _declarative_base(**kw)
1435
+ else:
1436
+ Base = declarative_base
1437
+
1438
+ return type(
1439
+ Base.__name__,
1440
+ (AutomapBase, Base),
1441
+ {
1442
+ "__abstract__": True,
1443
+ "classes": util.Properties({}),
1444
+ "by_module": util.Properties({}),
1445
+ "_sa_automapbase_bookkeeping": _Bookkeeping(set()),
1446
+ },
1447
+ )
1448
+
1449
+
1450
+ def _is_many_to_many(
1451
+ automap_base: Type[Any], table: Table
1452
+ ) -> Tuple[
1453
+ Optional[Table], Optional[Table], Optional[list[ForeignKeyConstraint]]
1454
+ ]:
1455
+ fk_constraints = [
1456
+ const
1457
+ for const in table.constraints
1458
+ if isinstance(const, ForeignKeyConstraint)
1459
+ ]
1460
+ if len(fk_constraints) != 2:
1461
+ return None, None, None
1462
+
1463
+ cols: List[Column[Any]] = sum(
1464
+ [
1465
+ [fk.parent for fk in fk_constraint.elements]
1466
+ for fk_constraint in fk_constraints
1467
+ ],
1468
+ [],
1469
+ )
1470
+
1471
+ if set(cols) != set(table.c):
1472
+ return None, None, None
1473
+
1474
+ return (
1475
+ fk_constraints[0].elements[0].column.table,
1476
+ fk_constraints[1].elements[0].column.table,
1477
+ fk_constraints,
1478
+ )
1479
+
1480
+
1481
+ def _relationships_for_fks(
1482
+ automap_base: Type[Any],
1483
+ map_config: _DeferredMapperConfig,
1484
+ table_to_map_config: Union[
1485
+ Dict[Optional[Table], _DeferredMapperConfig],
1486
+ Dict[Table, _DeferredMapperConfig],
1487
+ ],
1488
+ collection_class: type,
1489
+ name_for_scalar_relationship: NameForScalarRelationshipType,
1490
+ name_for_collection_relationship: NameForCollectionRelationshipType,
1491
+ generate_relationship: GenerateRelationshipType,
1492
+ ) -> None:
1493
+ local_table = cast("Optional[Table]", map_config.local_table)
1494
+ local_cls = cast(
1495
+ "Optional[Type[Any]]", map_config.cls
1496
+ ) # derived from a weakref, may be None
1497
+
1498
+ if local_table is None or local_cls is None:
1499
+ return
1500
+ for constraint in local_table.constraints:
1501
+ if isinstance(constraint, ForeignKeyConstraint):
1502
+ fks = constraint.elements
1503
+ referred_table = fks[0].column.table
1504
+ referred_cfg = table_to_map_config.get(referred_table, None)
1505
+ if referred_cfg is None:
1506
+ continue
1507
+ referred_cls = referred_cfg.cls
1508
+
1509
+ if local_cls is not referred_cls and issubclass(
1510
+ local_cls, referred_cls
1511
+ ):
1512
+ continue
1513
+
1514
+ relationship_name = name_for_scalar_relationship(
1515
+ automap_base, local_cls, referred_cls, constraint
1516
+ )
1517
+ backref_name = name_for_collection_relationship(
1518
+ automap_base, referred_cls, local_cls, constraint
1519
+ )
1520
+
1521
+ o2m_kws: Dict[str, Union[str, bool]] = {}
1522
+ nullable = False not in {fk.parent.nullable for fk in fks}
1523
+ if not nullable:
1524
+ o2m_kws["cascade"] = "all, delete-orphan"
1525
+
1526
+ if (
1527
+ constraint.ondelete
1528
+ and constraint.ondelete.lower() == "cascade"
1529
+ ):
1530
+ o2m_kws["passive_deletes"] = True
1531
+ else:
1532
+ if (
1533
+ constraint.ondelete
1534
+ and constraint.ondelete.lower() == "set null"
1535
+ ):
1536
+ o2m_kws["passive_deletes"] = True
1537
+
1538
+ create_backref = backref_name not in referred_cfg.properties
1539
+
1540
+ if relationship_name not in map_config.properties:
1541
+ if create_backref:
1542
+ backref_obj = generate_relationship(
1543
+ automap_base,
1544
+ interfaces.ONETOMANY,
1545
+ backref,
1546
+ backref_name,
1547
+ referred_cls,
1548
+ local_cls,
1549
+ collection_class=collection_class,
1550
+ **o2m_kws,
1551
+ )
1552
+ else:
1553
+ backref_obj = None
1554
+ rel = generate_relationship(
1555
+ automap_base,
1556
+ interfaces.MANYTOONE,
1557
+ relationship,
1558
+ relationship_name,
1559
+ local_cls,
1560
+ referred_cls,
1561
+ foreign_keys=[fk.parent for fk in constraint.elements],
1562
+ backref=backref_obj,
1563
+ remote_side=[fk.column for fk in constraint.elements],
1564
+ )
1565
+ if rel is not None:
1566
+ map_config.properties[relationship_name] = rel
1567
+ if not create_backref:
1568
+ referred_cfg.properties[
1569
+ backref_name
1570
+ ].back_populates = relationship_name # type: ignore[union-attr] # noqa: E501
1571
+ elif create_backref:
1572
+ rel = generate_relationship(
1573
+ automap_base,
1574
+ interfaces.ONETOMANY,
1575
+ relationship,
1576
+ backref_name,
1577
+ referred_cls,
1578
+ local_cls,
1579
+ foreign_keys=[fk.parent for fk in constraint.elements],
1580
+ back_populates=relationship_name,
1581
+ collection_class=collection_class,
1582
+ **o2m_kws,
1583
+ )
1584
+ if rel is not None:
1585
+ referred_cfg.properties[backref_name] = rel
1586
+ map_config.properties[
1587
+ relationship_name
1588
+ ].back_populates = backref_name # type: ignore[union-attr]
1589
+
1590
+
1591
+ def _m2m_relationship(
1592
+ automap_base: Type[Any],
1593
+ lcl_m2m: Table,
1594
+ rem_m2m: Table,
1595
+ m2m_const: List[ForeignKeyConstraint],
1596
+ table: Table,
1597
+ table_to_map_config: Union[
1598
+ Dict[Optional[Table], _DeferredMapperConfig],
1599
+ Dict[Table, _DeferredMapperConfig],
1600
+ ],
1601
+ collection_class: type,
1602
+ name_for_scalar_relationship: NameForCollectionRelationshipType,
1603
+ name_for_collection_relationship: NameForCollectionRelationshipType,
1604
+ generate_relationship: GenerateRelationshipType,
1605
+ ) -> None:
1606
+ map_config = table_to_map_config.get(lcl_m2m, None)
1607
+ referred_cfg = table_to_map_config.get(rem_m2m, None)
1608
+ if map_config is None or referred_cfg is None:
1609
+ return
1610
+
1611
+ local_cls = map_config.cls
1612
+ referred_cls = referred_cfg.cls
1613
+
1614
+ relationship_name = name_for_collection_relationship(
1615
+ automap_base, local_cls, referred_cls, m2m_const[0]
1616
+ )
1617
+ backref_name = name_for_collection_relationship(
1618
+ automap_base, referred_cls, local_cls, m2m_const[1]
1619
+ )
1620
+
1621
+ create_backref = backref_name not in referred_cfg.properties
1622
+
1623
+ if table in table_to_map_config:
1624
+ overlaps = "__*"
1625
+ else:
1626
+ overlaps = None
1627
+
1628
+ if relationship_name not in map_config.properties:
1629
+ if create_backref:
1630
+ backref_obj = generate_relationship(
1631
+ automap_base,
1632
+ interfaces.MANYTOMANY,
1633
+ backref,
1634
+ backref_name,
1635
+ referred_cls,
1636
+ local_cls,
1637
+ collection_class=collection_class,
1638
+ overlaps=overlaps,
1639
+ )
1640
+ else:
1641
+ backref_obj = None
1642
+
1643
+ rel = generate_relationship(
1644
+ automap_base,
1645
+ interfaces.MANYTOMANY,
1646
+ relationship,
1647
+ relationship_name,
1648
+ local_cls,
1649
+ referred_cls,
1650
+ overlaps=overlaps,
1651
+ secondary=table,
1652
+ primaryjoin=and_(
1653
+ fk.column == fk.parent for fk in m2m_const[0].elements
1654
+ ), # type: ignore [arg-type]
1655
+ secondaryjoin=and_(
1656
+ fk.column == fk.parent for fk in m2m_const[1].elements
1657
+ ), # type: ignore [arg-type]
1658
+ backref=backref_obj,
1659
+ collection_class=collection_class,
1660
+ )
1661
+ if rel is not None:
1662
+ map_config.properties[relationship_name] = rel
1663
+
1664
+ if not create_backref:
1665
+ referred_cfg.properties[
1666
+ backref_name
1667
+ ].back_populates = relationship_name # type: ignore[union-attr] # noqa: E501
1668
+ elif create_backref:
1669
+ rel = generate_relationship(
1670
+ automap_base,
1671
+ interfaces.MANYTOMANY,
1672
+ relationship,
1673
+ backref_name,
1674
+ referred_cls,
1675
+ local_cls,
1676
+ overlaps=overlaps,
1677
+ secondary=table,
1678
+ primaryjoin=and_(
1679
+ fk.column == fk.parent for fk in m2m_const[1].elements
1680
+ ), # type: ignore [arg-type]
1681
+ secondaryjoin=and_(
1682
+ fk.column == fk.parent for fk in m2m_const[0].elements
1683
+ ), # type: ignore [arg-type]
1684
+ back_populates=relationship_name,
1685
+ collection_class=collection_class,
1686
+ )
1687
+ if rel is not None:
1688
+ referred_cfg.properties[backref_name] = rel
1689
+ map_config.properties[
1690
+ relationship_name
1691
+ ].back_populates = backref_name # type: ignore[union-attr]