forked from pallets-eco/flask-sqlalchemy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_model_name.py
More file actions
266 lines (189 loc) · 7.61 KB
/
test_model_name.py
File metadata and controls
266 lines (189 loc) · 7.61 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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
from __future__ import annotations
import inspect
import typing as t
import pytest
import sqlalchemy as sa
import sqlalchemy.exc as sa_exc
import sqlalchemy.orm as sa_orm
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy.model import camel_to_snake_case
@pytest.mark.parametrize(
("name", "expect"),
[
("CamelCase", "camel_case"),
("Snake_case", "snake_case"),
("HTMLLayout", "html_layout"),
("LayoutHTML", "layout_html"),
("HTTP2Request", "http2_request"),
("ShoppingCartSession", "shopping_cart_session"),
("ABC", "abc"),
("PreABC", "pre_abc"),
("ABCPost", "abc_post"),
("PreABCPost", "pre_abc_post"),
("HTTP2RequestSession", "http2_request_session"),
("UserST4", "user_st4"),
(
"HTTP2ClientType3EncoderParametersSSE",
"http2_client_type3_encoder_parameters_sse",
),
(
"LONGName4TestingCamelCase2snake_caseXYZ",
"long_name4_testing_camel_case2snake_case_xyz",
),
("FooBarSSE2", "foo_bar_sse2"),
("AlarmMessageSS2SignalTransformer", "alarm_message_ss2_signal_transformer"),
("AstV2Node", "ast_v2_node"),
("HTTPResponseCodeXYZ", "http_response_code_xyz"),
("get2HTTPResponse123Code", "get2_http_response123_code"),
# ("getHTTPresponseCode", "get_htt_presponse_code"),
# ("__test__Method", "test___method"),
],
)
def test_camel_to_snake_case(name: str, expect: str) -> None:
assert camel_to_snake_case(name) == expect
def test_name(db: SQLAlchemy) -> None:
class FOOBar(db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class BazBar(db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class Ham(db.Model):
__tablename__ = "spam"
id = sa.Column(sa.Integer, primary_key=True)
assert FOOBar.__tablename__ == "foo_bar"
assert BazBar.__tablename__ == "baz_bar"
assert Ham.__tablename__ == "spam"
def test_single_name(db: SQLAlchemy) -> None:
"""Single table inheritance should not set a new name."""
class Duck(db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class Mallard(Duck):
pass
assert "__tablename__" not in Mallard.__dict__
assert Mallard.__tablename__ == "duck"
def test_joined_name(db: SQLAlchemy) -> None:
"""Model has a separate primary key; it should set a new name."""
class Duck(db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class Donald(Duck):
id = sa.Column(sa.Integer, sa.ForeignKey(Duck.id), primary_key=True)
assert Donald.__tablename__ == "donald"
def test_mixin_id(db: SQLAlchemy) -> None:
"""Primary key provided by mixin should still allow model to set
tablename.
"""
class Base:
id = sa.Column(sa.Integer, primary_key=True)
class Duck(Base, db.Model):
pass
assert not hasattr(Base, "__tablename__")
assert Duck.__tablename__ == "duck"
def test_mixin_attr(db: SQLAlchemy) -> None:
"""A declared attr tablename will be used down multiple levels of
inheritance.
"""
class Mixin:
@sa_orm.declared_attr # type: ignore[arg-type]
def __tablename__(cls) -> str: # noqa: B902
return cls.__name__.upper() # type: ignore[attr-defined,no-any-return]
class Bird(Mixin, db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class Duck(Bird):
# object reference
id = sa.Column(sa.Integer, sa.ForeignKey(Bird.id), primary_key=True)
class Mallard(Duck):
# string reference
id = sa.Column(sa.Integer, sa.ForeignKey("DUCK.id"), primary_key=True)
assert Bird.__tablename__ == "BIRD"
assert Duck.__tablename__ == "DUCK"
assert Mallard.__tablename__ == "MALLARD"
def test_abstract_name(db: SQLAlchemy) -> None:
"""Abstract model should not set a name. Subclass should set a name."""
class Base(db.Model):
__abstract__ = True
id = sa.Column(sa.Integer, primary_key=True)
class Duck(Base):
pass
assert "__tablename__" not in Base.__dict__
assert Duck.__tablename__ == "duck"
def test_complex_inheritance(db: SQLAlchemy) -> None:
"""Joined table inheritance, but the new primary key is provided by a
mixin, not directly on the class.
"""
class Duck(db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class IdMixin:
@sa_orm.declared_attr
def id(cls): # type: ignore[no-untyped-def] # noqa: B902
return sa.Column(sa.Integer, sa.ForeignKey(Duck.id), primary_key=True)
class RubberDuck(IdMixin, Duck): # type: ignore[misc]
pass
assert RubberDuck.__tablename__ == "rubber_duck"
def test_manual_name(db: SQLAlchemy) -> None:
"""Setting a manual name prevents generation for the immediate model. A
name is generated for joined but not single-table inheritance.
"""
class Duck(db.Model):
__tablename__ = "DUCK"
id = sa.Column(sa.Integer, primary_key=True)
type = sa.Column(sa.String)
__mapper_args__ = {"polymorphic_on": type}
class Daffy(Duck):
id = sa.Column(sa.Integer, sa.ForeignKey(Duck.id), primary_key=True)
__mapper_args__ = {"polymorphic_identity": "Tower"} # type: ignore[dict-item]
class Donald(Duck):
__mapper_args__ = {"polymorphic_identity": "Mouse"} # type: ignore[dict-item]
assert Duck.__tablename__ == "DUCK"
assert Daffy.__tablename__ == "daffy"
assert "__tablename__" not in Donald.__dict__
assert Donald.__tablename__ == "DUCK"
def test_primary_constraint(db: SQLAlchemy) -> None:
"""Primary key will be picked up from table args."""
class Duck(db.Model):
id = sa.Column(sa.Integer)
__table_args__ = (sa.PrimaryKeyConstraint(id),)
assert Duck.__table__ is not None
assert Duck.__tablename__ == "duck"
def test_no_access_to_class_property(db: SQLAlchemy) -> None:
"""Ensure the implementation doesn't access class properties or declared
attrs while inspecting the unmapped model.
"""
class class_property:
def __init__(self, f: t.Callable[..., t.Any]) -> None:
self.f = f
def __get__(self, instance: t.Any, owner: type[t.Any]) -> t.Any:
return self.f(owner)
class Duck(db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class ns:
is_duck = False
floats = False
class Witch(Duck):
@sa_orm.declared_attr # type: ignore[arg-type]
def is_duck(self) -> None:
# declared attrs will be accessed during mapper configuration,
# but make sure they're not accessed before that
info = inspect.getouterframes(inspect.currentframe())[2]
assert info[3] != "_should_set_tablename"
ns.is_duck = True
@class_property
def floats(self) -> None:
ns.floats = True
assert ns.is_duck
assert not ns.floats
def test_metadata_has_table(db: SQLAlchemy) -> None:
user = db.Table("user", sa.Column("id", sa.Integer, primary_key=True))
class User(db.Model):
pass
assert User.__table__ is user
def test_correct_error_for_no_primary_key(db: SQLAlchemy) -> None:
with pytest.raises(sa_exc.ArgumentError) as info:
class User(db.Model):
pass
assert "could not assemble any primary key" in str(info.value)
def test_single_has_parent_table(db: SQLAlchemy) -> None:
class Duck(db.Model):
id = sa.Column(sa.Integer, primary_key=True)
class Call(Duck):
pass
assert Call.__table__ is Duck.__table__
assert "__table__" not in Call.__dict__