Skip to content

Commit c2491f0

Browse files
author
quantmind
committed
Add dont_load method to Query. coverage at 90%
1 parent 5e32ad1 commit c2491f0

File tree

22 files changed

+222
-148
lines changed

22 files changed

+222
-148
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ Ver. 0.7.0 - Development
1818
* Added :class:`stdnet.orm.Session` for managing transactions in the object
1919
relational mapper.
2020
* Moved structures from :mod:`stdnet.backends` to the :mod:`stdnet.orm` module.
21-
* Added :meth:`stdnet.orm.Query.load_only` method for loading a subset
22-
of a model fields. This can improve performance by reducing the amount of
21+
* Added :meth:`stdnet.orm.Query.load_only` and :meth:`stdnet.orm.Query.dont_load`
22+
methods for loading a subset of a model fields.
23+
This can improve performance by reducing the amount of
2324
data transferred from the server to the client.
2425
Check the :ref:`performance tips <performance-loadonly>` regarding the
2526
new feature.
@@ -43,7 +44,7 @@ Ver. 0.7.0 - Development
4344
using python 2.6.
4445
* Moved the contrib module to :mod:`stdnet.apps`.
4546
* Added :mod:`stdnet.utils.dates`.
46-
* **556 regression tests** with **88%** coverage.
47+
* **568 regression tests** with **90%** coverage.
4748

4849
.. _vers06:
4950

stdnet/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def get_version():
1212
CLASSIFIERS = [
1313
'Development Status :: 4 - Beta',
1414
'Environment :: Plugins',
15+
'Environment :: Console',
1516
'Environment :: Web Environment',
1617
'Intended Audience :: Developers',
1718
'License :: OSI Approved :: BSD License',
@@ -23,7 +24,8 @@ def get_version():
2324
'Programming Language :: Python :: 3.2',
2425
'Programming Language :: Python :: 3.3',
2526
'Topic :: Utilities',
26-
'Topic :: Database'
27+
'Topic :: Database',
28+
'Topic :: Internet'
2729
]
2830

2931

stdnet/backends/main.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from .base import BackendDataServer
99

1010

11-
__all__ = ['getdb', 'getcache', 'CacheClass']
11+
__all__ = ['getdb', 'getcache']
1212

1313

1414
BACKENDS = {
@@ -82,17 +82,4 @@ def getcache(backend_uri=None, encoder = encoders.PythonPickle, **kwargs):
8282
db = getdb(backend_uri = backend_uri, pickler = encoder, **kwargs)
8383
return db.as_cache()
8484

85-
86-
class CacheClass(object):
87-
'''Class which can be used as django cache backend'''
88-
89-
def __init__(self, host, params):
90-
scheme = params.pop('type','redis')
91-
self.db = _getdb(scheme, host, params)
92-
self.timeout = self.db.default_timeout
93-
self.get = self.db.get
94-
self.set = self.db.set
95-
self.delete = self.db.delete
96-
self.has_key = self.db.has_key
97-
self.clear = self.db.clear
9885

stdnet/lib/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#First try local
22
try:
33
from .hr import *
4-
hasextensions = True
4+
hasextensions = True # pragma nocover
55
except ImportError:
66
# Try Global
77
try:
88
from hr import *
9-
hasextensions = True
9+
hasextensions = True # pragma nocover
1010
except ImportError:
1111
hasextensions = False
1212
from .fallback import *

stdnet/orm/base.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -302,13 +302,6 @@ def backend_fields(self, fields):
302302
names.append(name)
303303
atts.append(name)
304304
return names,atts
305-
306-
def multifields_ids_todelete(self, instance):
307-
'''Return the list of ids of :class:`MultiField` belonging to *instance*
308-
which needs to be deleted when *instance* is deleted.'''
309-
gen = (field.id(instance) for field in self.multifields\
310-
if field.todelete())
311-
return [fid for fid in gen if fid]
312305

313306

314307
class autoincrement(object):

stdnet/orm/fields.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,6 @@ def get_attname(self):
232232
def get_cache_name(self):
233233
return '_%s_cache' % self.name
234234

235-
def add(self, *args, **kwargs):
236-
raise NotImplementedError("Cannot add to field")
237-
238235
def id(self, obj):
239236
'''Field id for object *obj*, if applicable. Default is ``None``.'''
240237
return None

stdnet/orm/mapper.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def register(model, backend = None, ignore_duplicates = True,
100100
if model in _GLOBAL_REGISTRY:
101101
if not ignore_duplicates:
102102
raise AlreadyRegistered(
103-
'Model {0} is already registered'.format(meta))
103+
'Model {0} is already registered'.format(model._meta))
104104
else:
105105
return
106106
backend = getdb(backend_uri = backend, **params)
@@ -228,14 +228,5 @@ def register_applications(applications, **kwargs):
228228
return list(register_application_models(applications,**kwargs))
229229

230230

231-
def register_query_for_model(model, query_function):
232-
_SPECIAL_QUERIES[model] = query_function
233-
234-
def get_model_query(model):
235-
return _SPECIAL_QUERIES.get(model)
236-
237-
238231
_GLOBAL_REGISTRY = set()
239232

240-
_SPECIAL_QUERIES = {}
241-

stdnet/orm/query.py

Lines changed: 64 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ def backend(self):
4848

4949

5050
class Q(object):
51+
'''Base class for :class:`Query` and :class:`QueryElem`'''
5152
keyword = ''
5253
name = ''
5354
def __init__(self, meta, session, select_related = None,
54-
ordering = None, fields = None, get_field = None,
55-
name = None, keyword = None):
55+
ordering = None, fields = None,
56+
get_field = None, name = None, keyword = None):
5657
self._meta = meta
5758
self.session = session
5859
self.data = {'select_related': select_related,
@@ -143,28 +144,6 @@ def _clone(self):
143144
return q
144145

145146

146-
class EmptyQuery(Q):
147-
keyword = 'empty'
148-
def items(self, slic):
149-
return []
150-
151-
def __len__(self):
152-
return 0
153-
154-
def __iter__(self):
155-
return iter(())
156-
157-
def count(self):
158-
return 0
159-
160-
def construct(self):
161-
return self
162-
163-
@property
164-
def executed(self):
165-
return True
166-
167-
168147
class QueryElement(Q):
169148
'''An element of a :class:`Query`.
170149
@@ -225,14 +204,6 @@ def valid(self):
225204
return self.keyword == 'set'
226205
else:
227206
return len(self.underlying) > 0
228-
229-
def flat(self):
230-
yield self.keyword
231-
yield self.backend(self.meta)
232-
yield self.get
233-
for b in self.body:
234-
yield b
235-
yield self.get
236207

237208

238209
class QuerySet(QueryElement):
@@ -268,8 +239,44 @@ def difference(queries):
268239
def queryset(qs, **kwargs):
269240
return QuerySet(qs._meta,qs.session,**kwargs)
270241

271-
272-
class Query(Q):
242+
243+
class QueryBase(Q):
244+
245+
def __iter__(self):
246+
return iter(self.items())
247+
248+
def __len__(self):
249+
return self.count()
250+
251+
def all(self):
252+
'''Return a ``list`` of all matched elements in this :class:`Query`.'''
253+
return self.items()
254+
255+
256+
class EmptyQuery(QueryBase):
257+
'''Degenerate :class:`QueryBase` simulating and empty set.'''
258+
keyword = 'empty'
259+
def items(self, slic = None):
260+
return []
261+
262+
def count(self):
263+
return 0
264+
265+
def construct(self):
266+
return self
267+
268+
@property
269+
def executed(self):
270+
return True
271+
272+
def union(self, query, *queries):
273+
return query.union(*queries)
274+
275+
def intersect(self, *queries):
276+
return self
277+
278+
279+
class Query(QueryBase):
273280
'''A :class:`Query` is produced in terms of a given :class:`Session`,
274281
using the :meth:`Session.query` method::
275282
@@ -353,6 +360,7 @@ def __init__(self, *args, **kwargs):
353360
self.unions = kwargs.pop('unions',())
354361
self.intersections = kwargs.pop('intersections',())
355362
self.text = kwargs.pop('text',None)
363+
self.exclude_fields = kwargs.pop('exclude_fields',None)
356364
super(Query,self).__init__(*args,**kwargs)
357365
self.clear()
358366

@@ -380,16 +388,6 @@ def __getitem__(self, slic):
380388
if isinstance(slic,slice):
381389
return self.items(slic)
382390
return self.items()[slic]
383-
384-
def __iter__(self):
385-
return iter(self.items())
386-
387-
def __len__(self):
388-
return self.count()
389-
390-
def all(self):
391-
'''Return a ``list`` of all matched elements in this :class:`Query`.'''
392-
return self.items()
393391

394392
def filter(self, **kwargs):
395393
'''Create a new :class:`Query` with additional clauses corresponding to
@@ -550,16 +548,17 @@ def load_only(self, *fields):
550548
q.data['fields'] = tuple(fs) if fs else None
551549
return q
552550

553-
def _load_only(self, fields):
554-
dfields = self._meta.dfields
555-
for name in fields:
556-
if name in dfields:
557-
yield name
558-
else:
559-
# It may be a JSONFiled
560-
na = name.split(JSPLITTER)[0]
561-
if na in dfields and dfields[na].type == 'json object':
562-
yield name
551+
def dont_load(self, *fields):
552+
'''Works like :meth:`load_only` to provides a
553+
:ref:`performance boost <increase-performance>` in cases when you need
554+
to load all fields except a subset specified by *fields*.
555+
'''
556+
q = self._clone()
557+
fs = set(q.exclude_fields) if q.exclude_fields else set()
558+
if fields:
559+
fs.update(fields)
560+
q.exclude_fields = tuple(fs) if fs else None
561+
return q
563562

564563
def get(self, **kwargs):
565564
'''Return an instance of a model matching the query. A special case is
@@ -611,12 +610,7 @@ def backend_query(self, **kwargs):
611610
This is a lazy method in the sense that it is evaluated once only and its
612611
result stored for future retrieval.'''
613612
q = self.construct()
614-
if q is None:
615-
return EmptyQuery(self._meta, self.session)
616-
elif isinstance(q, EmptyQuery):
617-
return q
618-
else:
619-
return q.backend_query(**kwargs)
613+
return q if isinstance(q, EmptyQuery) else q.backend_query(**kwargs)
620614

621615
def test_unique(self, fieldname, value, instance = None, exception = None):
622616
'''Test if a given field *fieldname* has a unique *value*
@@ -694,7 +688,16 @@ def _construct(self):
694688
q = union((q,)+self.unions)
695689

696690
q = self.search_queries(q)
697-
q.data = self.data.copy()
691+
data = self.data.copy()
692+
if self.exclude_fields:
693+
fields = data['fields']
694+
if not fields:
695+
fields = set((f.name for f in self._meta.scalarfields))
696+
else:
697+
fields = set(fields)
698+
fields.difference_update(self.exclude_fields)
699+
data['fields'] = fields
700+
q.data = data
698701
return q
699702

700703
def aggregate(self, kwargs):
@@ -767,9 +770,4 @@ def items(self, slic = None):
767770
cache[key] = seq
768771
return seq
769772

770-
771-
class QueryGroup(object):
772-
773-
def __init__(self):
774-
self.queries = []
775773

stdnet/test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
if sys.version_info >= (2,7):
1212
import unittest
13-
else:
13+
else: # pragma nocover
1414
try:
1515
import unittest2 as unittest
1616
except ImportError:

stdnet/utils/dispatch/dispatcher.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010

1111

1212
def _make_id(target):
13-
if hasattr(target, 'im_func'):
14-
return (id(target.im_self), id(target.im_func))
13+
if hasattr(target, '__func__'):
14+
return (id(target.__self__), id(target.__func__))
1515
return id(target)
1616

17+
1718
class Signal(object):
1819
"""
1920
Base class for all signals. From PyDispatcher and django.

0 commit comments

Comments
 (0)