@@ -182,7 +182,8 @@ def should_set_tablename(cls: type) -> bool:
182
182
joined-table inheritance. If no primary key is found, the name will be unset.
183
183
"""
184
184
if cls .__dict__ .get ("__abstract__" , False ) or not any (
185
- isinstance (b , sa .orm .DeclarativeMeta ) for b in cls .__mro__ [1 :]
185
+ (isinstance (b , sa .orm .DeclarativeMeta ) or b is sa .orm .DeclarativeBase )
186
+ for b in cls .__mro__ [1 :]
186
187
):
187
188
return False
188
189
@@ -196,7 +197,10 @@ def should_set_tablename(cls: type) -> bool:
196
197
return not (
197
198
base is cls
198
199
or base .__dict__ .get ("__abstract__" , False )
199
- or not isinstance (base , sa .orm .DeclarativeMeta )
200
+ or not (
201
+ isinstance (base , sa .orm .DeclarativeMeta )
202
+ or base is sa .orm .DeclarativeBase
203
+ )
200
204
)
201
205
202
206
return True
@@ -212,3 +216,99 @@ class DefaultMeta(BindMetaMixin, NameMetaMixin, sa.orm.DeclarativeMeta):
212
216
"""SQLAlchemy declarative metaclass that provides ``__bind_key__`` and
213
217
``__tablename__`` support.
214
218
"""
219
+
220
+
221
+ class DefaultMixin :
222
+ """A mixin that provides Flask-SQLAlchemy default functionality:
223
+ * sets a model's ``__tablename__`` by converting the
224
+ ``CamelCase`` class name to ``snake_case``. A name is set for non-abstract models
225
+ that do not otherwise define ``__tablename__``. If a model does not define a primary
226
+ key, it will not generate a name or ``__table__``, for single-table inheritance.
227
+ * sets a model's ``metadata`` based on its ``__bind_key__``.
228
+ If the model sets ``metadata`` or ``__table__`` directly, ``__bind_key__`` is
229
+ ignored. If the ``metadata`` is the same as the parent model, it will not be set
230
+ directly on the child model.
231
+ * Provides a default repr based on the model's primary key.
232
+ """
233
+
234
+ __fsa__ : SQLAlchemy
235
+ metadata : sa .MetaData
236
+ __tablename__ : str
237
+ __table__ : sa .Table
238
+
239
+ def __init_subclass__ (cls , ** kwargs ):
240
+ if not ("metadata" in cls .__dict__ or "__table__" in cls .__dict__ ):
241
+ bind_key = getattr (cls , "__bind_key__" , None )
242
+ parent_metadata = getattr (cls , "metadata" , None )
243
+ metadata = cls .__fsa__ ._make_metadata (bind_key )
244
+
245
+ if metadata is not parent_metadata :
246
+ cls .metadata = metadata
247
+
248
+ if should_set_tablename (cls ):
249
+ cls .__tablename__ = camel_to_snake_case (cls .__name__ )
250
+
251
+ super ().__init_subclass__ (** kwargs )
252
+
253
+ # __table_cls__ has run. If no table was created, use the parent table.
254
+ if (
255
+ "__tablename__" not in cls .__dict__
256
+ and "__table__" in cls .__dict__
257
+ and cls .__dict__ ["__table__" ] is None
258
+ ):
259
+ del cls .__table__
260
+
261
+ @classmethod
262
+ def __table_cls__ (cls , * args : t .Any , ** kwargs : t .Any ) -> sa .Table | None :
263
+ """This is called by SQLAlchemy during mapper setup. It determines the final
264
+ table object that the model will use.
265
+
266
+ If no primary key is found, that indicates single-table inheritance, so no table
267
+ will be created and ``__tablename__`` will be unset.
268
+ """
269
+ schema = kwargs .get ("schema" )
270
+
271
+ if schema is None :
272
+ key = args [0 ]
273
+ else :
274
+ key = f"{ schema } .{ args [0 ]} "
275
+
276
+ # Check if a table with this name already exists. Allows reflected tables to be
277
+ # applied to models by name.
278
+ if key in cls .metadata .tables :
279
+ return sa .Table (* args , ** kwargs )
280
+
281
+ # If a primary key is found, create a table for joined-table inheritance.
282
+ for arg in args :
283
+ if (isinstance (arg , sa .Column ) and arg .primary_key ) or isinstance (
284
+ arg , sa .PrimaryKeyConstraint
285
+ ):
286
+ return sa .Table (* args , ** kwargs )
287
+
288
+ # If no base classes define a table, return one that's missing a primary key
289
+ # so SQLAlchemy shows the correct error.
290
+ for base in cls .__mro__ [1 :- 1 ]:
291
+ if "__table__" in base .__dict__ :
292
+ break
293
+ else :
294
+ return sa .Table (* args , ** kwargs )
295
+
296
+ # Single-table inheritance, use the parent table name. __init__ will unset
297
+ # __table__ based on this.
298
+ if "__tablename__" in cls .__dict__ :
299
+ del cls .__tablename__
300
+
301
+ return None
302
+
303
+ def __repr__ (self ) -> str :
304
+ state = sa .inspect (self )
305
+ assert state is not None
306
+
307
+ if state .transient :
308
+ pk = f"(transient { id (self )} )"
309
+ elif state .pending :
310
+ pk = f"(pending { id (self )} )"
311
+ else :
312
+ pk = ", " .join (map (str , state .identity ))
313
+
314
+ return f"<{ type (self ).__name__ } { pk } >"
0 commit comments