Skip to content

Commit

Permalink
Fixes #281, added none_as_none transitive feature
Browse files Browse the repository at this point in the history
  • Loading branch information
fantix committed Jul 27, 2018
1 parent b09161c commit 789888a
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 15 deletions.
4 changes: 4 additions & 0 deletions gino/crud.py
Expand Up @@ -678,6 +678,10 @@ def distinct(cls, *columns):
"""
return cls.load().distinct(*columns)

@classmethod
def none_as_none(cls, enabled=True):
return cls.load().none_as_none(enabled)

@classmethod
def alias(cls, *args, **kwargs):
"""
Expand Down
52 changes: 37 additions & 15 deletions gino/loader.py
@@ -1,3 +1,5 @@
import warnings

from sqlalchemy import select
from sqlalchemy.schema import Column

Expand Down Expand Up @@ -46,6 +48,9 @@ def __getattr__(self, item):
return getattr(self.query, item)


_none = object()


class ModelLoader(Loader):
def __init__(self, model, *column_names, **extras):
self.model = model
Expand All @@ -57,12 +62,20 @@ def __init__(self, model, *column_names, **extras):
self.extras = dict((key, self.get(value))
for key, value in extras.items())
self.on_clause = None

def _do_load(self, row):
self._none_as_none = None

def _do_load(self, row, *, none_as_none=None):
if none_as_none is None:
none_as_none = self._none_as_none
if none_as_none is None:
warnings.warn(
'The none_as_none feature will be enabled by default in 0.8',
DeprecationWarning)
values = dict((c.name, row[c]) for c in self.columns if c in row)
if none_as_none and all((v is None) for v in values.values()):
return None
rv = self.model()
for c in self.columns:
if c in row:
rv.__values__[c.name] = row[c]
rv.__values__.update(values)
return rv

def do_load(self, row, context):
Expand All @@ -72,22 +85,23 @@ def do_load(self, row, context):
context = {}
ctx = context.setdefault(self._distinct, {})
key = tuple(row[col] for col in self._distinct)
if key == (None,) * len(key):
return None, None
rv = ctx.get(key)
if rv is None:
rv = self._do_load(row)
rv = ctx.get(key, _none)
if rv is _none:
rv = self._do_load(row, none_as_none=True)
ctx[key] = rv
else:
distinct = False
else:
rv = self._do_load(row)

for key, value in self.extras.items():
value, distinct_ = value.do_load(row, context)
if distinct_ is not None:
setattr(rv, key, value)
return rv, distinct
if rv is None:
return None, None
else:
for key, value in self.extras.items():
value, distinct_ = value.do_load(row, context)
if distinct_ is not None:
setattr(rv, key, value)
return rv, distinct

def get_columns(self):
yield from self.columns
Expand Down Expand Up @@ -118,6 +132,14 @@ def distinct(self, *columns):
self._distinct = columns
return self

def none_as_none(self, enabled=True):
if not enabled:
warnings.warn(
'The none_as_none feature will be always enabled in 0.9',
PendingDeprecationWarning)
self._none_as_none = enabled
return self


class AliasLoader(ModelLoader):
def __init__(self, alias, *column_names, **extras):
Expand Down
24 changes: 24 additions & 0 deletions tests/test_loader.py
Expand Up @@ -238,3 +238,27 @@ async def test_tuple_loader_279(user):
assert len(row) == 2
async for row in query.gino.load(TupleLoader((User, Team))).iterate():
assert len(row) == 2


async def test_281_none_as_none(user):
import gino

if gino.__version__ < '0.9':
query = Team.outerjoin(User).select()
loader = Team, User.none_as_none()
assert any(row[1] is None
for row in await query.gino.load(loader).all())

loader = Team.distinct(Team.id).load(add_member=User.none_as_none())
assert any(not team.members
for team in await query.gino.load(loader).all())

if gino.__version__ >= '0.8.0':
query = Team.outerjoin(User).select()
loader = Team, User
assert any(row[1] is None
for row in await query.gino.load(loader).all())

loader = Team.distinct(Team.id).load(add_member=User)
assert any(not team.members
for team in await query.gino.load(loader).all())

0 comments on commit 789888a

Please sign in to comment.