Skip to content

Commit f811c92

Browse files
aragentumfantix
andauthored
Fix multiple JSON profiles issue in one model (#696)
* fix multiple json profiles * rename __profile__ to __profiles__ * fix test * reformat code * update authors * revert solution * add a new solution * apply suggestions * update json_prop_names to set * optimize set usage Co-authored-by: Fantix King <[email protected]>
1 parent 804fdc8 commit f811c92

File tree

6 files changed

+86
-8
lines changed

6 files changed

+86
-8
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Contributors
4141
* Tiago Requeijo <[email protected]>
4242
4343
* Iuliia Volkova <[email protected]>
44+
* Roman Averchenkov <[email protected]>
4445

4546

4647
Special thanks to my wife Daisy and her outsourcing company `DecentFoX Studio`_,

src/gino/declarative.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,14 @@ def _init_table(cls, sub_cls):
350350
rv = sa.Table(table_name, sub_cls.__metadata__, *args, **table_kw)
351351
for k, v in updates.items():
352352
setattr(sub_cls, k, v)
353+
354+
json_prop_names = set()
353355
for each_cls in sub_cls.__mro__[::-1]:
354356
for k, v in each_cls.__dict__.items():
355357
if isinstance(v, json_support.JSONProperty):
356358
if not v.name:
357359
v.name = k
360+
json_prop_names.add(v.prop_name)
358361
json_col = getattr(
359362
sub_cls.__dict__.get(v.prop_name), "column", None
360363
)
@@ -367,6 +370,7 @@ def _init_table(cls, sub_cls):
367370
type(v).__name__, v.name, v.prop_name,
368371
)
369372
)
373+
sub_cls.__json_prop_names__ = json_prop_names
370374
return rv
371375

372376

src/gino/json_support.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ def get_profile(self, instance):
5454
if instance.__profile__ is None:
5555
props = type(instance).__dict__
5656
instance.__profile__ = {}
57-
for key, value in (getattr(instance, self.prop_name, None) or {}).items():
57+
profiles = {}
58+
for prop_name in getattr(instance, "__json_prop_names__", set()):
59+
profiles.update(getattr(instance, prop_name, None) or {})
60+
for key, value in profiles.items():
5861
if key not in props:
5962
raise UnknownJSONPropertyError(
6063
"`{}` is found in `{}` of instance {}, "

tests/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,16 @@ class User(db.Model):
3939
id = db.Column(db.BigInteger(), primary_key=True)
4040
nickname = db.Column("name", db.Unicode(), default=_random_name)
4141
profile = db.Column("props", JSONB(), nullable=False, server_default="{}")
42+
parameter = db.Column("params", JSONB(), nullable=False, server_default="{}")
4243
type = db.Column(db.Enum(UserType), nullable=False, default=UserType.USER,)
4344
realname = db.StringProperty()
4445
age = db.IntegerProperty(default=18)
4546
balance = db.IntegerProperty(default=0)
4647
birthday = db.DateTimeProperty(default=lambda i: datetime.utcfromtimestamp(0))
4748
team_id = db.Column(db.ForeignKey("gino_teams.id"))
49+
weight = db.IntegerProperty(prop_name='parameter')
50+
height = db.IntegerProperty(default=170, prop_name='parameter')
51+
bio = db.StringProperty(prop_name='parameter')
4852

4953
@balance.after_get
5054
def balance(self, val):

tests/test_execution_options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ async def test_query_ext(bind):
4949
u.id,
5050
"test",
5151
{"age": 18, "birthday": "1970-01-01T00:00:00.000000"},
52+
{"height": 170},
5253
UserType.USER,
5354
None,
5455
)
@@ -59,6 +60,7 @@ async def test_query_ext(bind):
5960
u.id,
6061
"test",
6162
{"age": 18, "birthday": "1970-01-01T00:00:00.000000"},
63+
{"height": 170},
6264
UserType.USER,
6365
None,
6466
)

tests/test_json.py

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ async def test_in_memory():
1414
u.age += 10
1515
assert u.age == 28
1616
assert u.balance == 0
17+
assert u.height == 170
1718
assert isinstance(u.balance, float)
1819

1920

@@ -23,12 +24,15 @@ async def test_crud(bind):
2324

2425
now = datetime.utcnow()
2526
now_str = now.strftime(DATETIME_FORMAT)
26-
u = await User.create(nickname="fantix", birthday=now)
27+
u = await User.create(
28+
nickname="fantix", birthday=now, bio="I code in Python and more."
29+
)
2730
u.age += 1
2831
assert await u.query.gino.model(None).first() == (
2932
1,
3033
"fantix",
3134
{"age": 18, "birthday": now_str},
35+
{"bio": "I code in Python and more.", "height": 170},
3236
UserType.USER,
3337
None,
3438
)
@@ -38,23 +42,27 @@ async def test_crud(bind):
3842
assert u.birthday == now
3943
assert u.age == 18
4044
assert u.balance == 0
45+
assert u.height == 170
46+
assert u.bio == "I code in Python and more."
4147
assert isinstance(u.balance, float)
4248
assert await db.select([User.birthday]).where(User.id == u.id).gino.scalar() == now
4349

4450
# In-memory update, not applying
4551
u.update(birthday=now - timedelta(days=3650))
4652

4753
# Update two JSON fields, one using expression
48-
await u.update(age=User.age - 2, balance=100.85).apply()
54+
await u.update(age=User.age - 2, balance=100.85, height=180).apply()
4955

5056
assert u.birthday == now - timedelta(days=3650)
5157
assert u.age == 16
5258
assert u.balance == 100
59+
assert u.height == 180
5360
assert isinstance(u.balance, float)
5461
assert await u.query.gino.model(None).first() == (
5562
1,
5663
"fantix",
5764
dict(age=16, balance=100, birthday=now_str),
65+
dict(bio="I code in Python and more.", height=180),
5866
UserType.USER,
5967
None,
6068
)
@@ -63,12 +71,19 @@ async def test_crud(bind):
6371
# Reload and test updating both JSON and regular property
6472
u = await User.get(u.id)
6573
await u.update(
66-
age=User.age - 2, balance=200.15, realname="daisy", nickname="daisy.nick"
74+
age=User.age - 2,
75+
balance=200.15,
76+
realname="daisy",
77+
nickname="daisy.nick",
78+
height=185,
79+
weight=75,
6780
).apply()
81+
data = await u.query.gino.model(None).first()
6882
assert await u.query.gino.model(None).first() == (
6983
1,
7084
"daisy.nick",
7185
dict(age=14, balance=200, realname="daisy", birthday=now_str),
86+
dict(bio="I code in Python and more.", height=185, weight=75),
7287
UserType.USER,
7388
None,
7489
)
@@ -81,6 +96,9 @@ async def test_crud(bind):
8196
realname="daisy",
8297
type=UserType.USER,
8398
team_id=None,
99+
bio="I code in Python and more.",
100+
height=185,
101+
weight=75,
84102
)
85103

86104
# Deleting property doesn't affect database
@@ -130,12 +148,16 @@ class News(db.Model):
130148
# noinspection PyUnusedLocal
131149
async def test_reload(bind):
132150
u = await User.create()
133-
await u.update(realname=db.cast("888", db.Unicode)).apply()
151+
await u.update(realname=db.cast("888", db.Unicode), weight=75).apply()
134152
assert u.realname == "888"
135-
await u.update(profile=None).apply()
153+
assert u.weight == 75
154+
await u.update(profile=None, parameter=None).apply()
136155
assert u.realname == "888"
156+
assert u.weight == 75
137157
User.__dict__["realname"].reload(u)
158+
User.__dict__["weight"].reload(u)
138159
assert u.realname is None
160+
assert u.weight is None
139161

140162

141163
# noinspection PyUnusedLocal
@@ -151,29 +173,71 @@ class PropsTest(db.Model):
151173
obj = db.ObjectProperty()
152174
arr = db.ArrayProperty()
153175

176+
parameter = db.Column(JSONB(), nullable=False, server_default="{}")
177+
178+
raw_param = db.JSONProperty(prop_name="parameter")
179+
bool_param = db.BooleanProperty(prop_name="parameter")
180+
obj_param = db.ObjectProperty(prop_name="parameter")
181+
arr_param = db.ArrayProperty(prop_name="parameter")
182+
154183
await PropsTest.gino.create()
155184
try:
156185
t = await PropsTest.create(
157-
raw=dict(a=[1, 2]), bool=True, obj=dict(x=1, y=2), arr=[3, 4, 5, 6],
186+
raw=dict(a=[1, 2]),
187+
bool=True,
188+
obj=dict(x=1, y=2),
189+
arr=[3, 4, 5, 6],
190+
raw_param=dict(a=[3, 4]),
191+
bool_param=False,
192+
obj_param=dict(x=3, y=4),
193+
arr_param=[7, 8, 9, 10],
158194
)
159195
assert t.obj["x"] == 1
196+
assert t.obj_param["x"] == 3
160197
assert t.arr[-1] == 6
198+
assert t.arr_param[-1] == 10
199+
data = await db.select(
200+
[
201+
PropsTest.profile,
202+
PropsTest.parameter,
203+
PropsTest.raw,
204+
PropsTest.bool,
205+
PropsTest.obj_param,
206+
]
207+
).gino.first()
161208
assert await db.select(
162-
[PropsTest.profile, PropsTest.raw, PropsTest.bool,]
209+
[
210+
PropsTest.profile,
211+
PropsTest.parameter,
212+
PropsTest.raw,
213+
PropsTest.bool,
214+
PropsTest.obj_param,
215+
]
163216
).gino.first() == (
164217
{
165218
"arr": [3, 4, 5, 6],
166219
"obj": {"x": 1, "y": 2},
167220
"raw": {"a": [1, 2]},
168221
"bool": True,
169222
},
223+
{
224+
"arr_param": [7, 8, 9, 10],
225+
"obj_param": {"x": 3, "y": 4},
226+
"raw_param": {"a": [3, 4]},
227+
"bool_param": False,
228+
},
170229
dict(a=[1, 2]),
171230
True,
231+
dict(x=3, y=4),
172232
)
173233
t.obj = dict(x=10, y=20)
234+
t.obj_param = dict(x=30, y=45)
174235
assert t.obj["x"] == 10
236+
assert t.obj_param["y"] == 45
175237
t.arr = [4, 5, 6, 7]
238+
t.arr_param = [11, 12, 13, 14, 15]
176239
assert t.arr[-1] == 7
240+
assert t.arr_param[-1] == 15
177241
finally:
178242
await PropsTest.gino.drop()
179243

0 commit comments

Comments
 (0)