Skip to content

Commit 1a48cc5

Browse files
authored
Merge pull request #585 from wwwjfy/model-in-query
fix #573, simplify loading model from subqueries
2 parents 42adff2 + 5c821fa commit 1a48cc5

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

gino/crud.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import inspect
12
import itertools
23
import weakref
34

@@ -724,3 +725,52 @@ def alias(cls, *args, **kwargs):
724725
725726
"""
726727
return Alias(cls, *args, **kwargs)
728+
729+
@classmethod
730+
def in_query(cls, query):
731+
"""
732+
Convenient method to get a Model object when using subqueries.
733+
734+
Though with filters and aggregations, subqueries often return same
735+
columns as the original table, but SQLAlchemy could not recognize them
736+
as the columns are in subqueries, so technically they're columns in the
737+
new "table".
738+
739+
With this method, the columns are loaded into the origintal models when
740+
being used in subquries. For example::
741+
742+
query = query.alias('users')
743+
MyUser = User.in_query(query)
744+
745+
loader = MyUser.distinct(User1.id).load()
746+
users = await query.gino.load(loader).all()
747+
748+
"""
749+
return _get_query_model(cls, query)
750+
751+
752+
def _get_query_model(model, query):
753+
return QueryModel(model.__name__, (), dict(_model=model, _query=query))
754+
755+
756+
class QueryModel(type):
757+
"""
758+
Metaclass of Model classes used for subqueries.
759+
760+
"""
761+
def __getattr__(self, item):
762+
rv = getattr(self._query.columns, item,
763+
getattr(self._model.__table__.columns, item,
764+
getattr(self._model, item, DEFAULT)))
765+
# replace `cls` in classmethod in models to `self`
766+
if inspect.ismethod(rv) and inspect.isclass(rv.__self__):
767+
return lambda *args, **kwargs: rv.__func__(self, *args, **kwargs)
768+
if rv is DEFAULT:
769+
raise AttributeError
770+
return rv
771+
772+
def __iter__(self):
773+
return iter(self._query.columns)
774+
775+
def __call__(self, *args, **kwargs):
776+
return self._model(*args, **kwargs)

tests/test_loader.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,18 @@ async def test_none_as_none_281(user):
351351
loader = Team.distinct(Team.id).load(add_member=User)
352352
assert any(not team.members
353353
for team in await query.gino.load(loader).all())
354+
355+
356+
async def test_model_in_query(user):
357+
query = select([User], from_obj=User.outerjoin(Team))
358+
query = query.where(Team.company_id==user.team.company.id)
359+
360+
query = query.alias('users')
361+
User1 = User.in_query(query)
362+
363+
query = query.outerjoin(Team).outerjoin(Company).select()
364+
loader = User1.distinct(User1.id).load()
365+
users = await query.gino.load(loader).all()
366+
assert users[0] != user
367+
assert users[0].id == user.id
368+
assert users[0].nickname == user.nickname

0 commit comments

Comments
 (0)