Repository navigation
Expand file tree
/
Copy pathmodel.py
More file actions
140 lines (105 loc) · 4.59 KB
/
Copy pathmodel.py
File metadata and controls
140 lines (105 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import re
import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.schema import _get_table_key
def should_set_tablename(cls):
"""Determine whether ``__tablename__`` should be automatically generated
for a model.
* If no class in the MRO sets a name, one should be generated.
* If a declared attr is found, it should be used instead.
* If a name is found, it should be used if the class is a mixin, otherwise
one should be generated.
* Abstract models should not have one generated.
Later, :meth:`._BoundDeclarativeMeta.__table_cls__` will determine if the
model looks like single or joined-table inheritance. If no primary key is
found, the name will be unset.
"""
if cls.__dict__.get("__abstract__", False) or not any(
isinstance(b, DeclarativeMeta) for b in cls.__mro__[1:]
):
return False
for base in cls.__mro__:
if "__tablename__" not in base.__dict__:
continue
if isinstance(base.__dict__["__tablename__"], declared_attr):
return False
return not (
base is cls
or base.__dict__.get("__abstract__", False)
or not isinstance(base, DeclarativeMeta)
)
return True
def camel_to_snake_case(name):
name = re.sub(r"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))", r"_\1", name)
return name.lower().lstrip("_")
class NameMetaMixin(type):
def __init__(cls, name, bases, d):
if should_set_tablename(cls):
cls.__tablename__ = camel_to_snake_case(cls.__name__)
super().__init__(name, bases, d)
# __table_cls__ has run at this point
# if no table was created, use the parent table
if (
"__tablename__" not in cls.__dict__
and "__table__" in cls.__dict__
and cls.__dict__["__table__"] is None
):
del cls.__table__
def __table_cls__(cls, *args, **kwargs):
"""This is called by SQLAlchemy during mapper setup. It determines the
final table object that the model will use.
If no primary key is found, that indicates single-table inheritance,
so no table will be created and ``__tablename__`` will be unset.
"""
# check if a table with this name already exists
# allows reflected tables to be applied to model by name
key = _get_table_key(args[0], kwargs.get("schema"))
if key in cls.metadata.tables:
return sa.Table(*args, **kwargs)
# if a primary key or constraint is found, create a table for
# joined-table inheritance
for arg in args:
if (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(
arg, sa.PrimaryKeyConstraint
):
return sa.Table(*args, **kwargs)
# if no base classes define a table, return one
# ensures the correct error shows up when missing a primary key
for base in cls.__mro__[1:-1]:
if "__table__" in base.__dict__:
break
else:
return sa.Table(*args, **kwargs)
# single-table inheritance, use the parent tablename
if "__tablename__" in cls.__dict__:
del cls.__tablename__
class BindMetaMixin(type):
def __init__(cls, name, bases, d):
bind_key = d.pop("__bind_key__", None) or getattr(cls, "__bind_key__", None)
super().__init__(name, bases, d)
if bind_key is not None and getattr(cls, "__table__", None) is not None:
cls.__table__.info["bind_key"] = bind_key
class DefaultMeta(NameMetaMixin, BindMetaMixin, DeclarativeMeta):
pass
class Model:
"""Base class for SQLAlchemy declarative base model.
To define models, subclass :attr:`db.Model <SQLAlchemy.Model>`, not this
class. To customize ``db.Model``, subclass this and pass it as
``model_class`` to :class:`SQLAlchemy`.
"""
#: Query class used by :attr:`query`. Defaults to
# :class:`SQLAlchemy.Query`, which defaults to :class:`BaseQuery`.
query_class = None
#: Convenience property to query the database for instances of this model
# using the current session. Equivalent to ``db.session.query(Model)``
# unless :attr:`query_class` has been changed.
query = None
def __repr__(self):
identity = inspect(self).identity
if identity is None:
pk = f"(transient {id(self)})"
else:
pk = ", ".join(str(value) for value in identity)
return f"<{type(self).__name__} {pk}>"