Skip to content

Commit 332e061

Browse files
Merge pull request #319 from eywalker/master
Update `fetch` and `fetch1` syntax
2 parents 17acda7 + 7ff082a commit 332e061

File tree

7 files changed

+364
-101
lines changed

7 files changed

+364
-101
lines changed

datajoint/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828

2929
print('DataJoint', __version__, '('+__date__+')')
3030

31-
logging.captureWarnings(True)
32-
3331

3432
class key:
3533
"""

datajoint/connection.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,17 @@ def query(self, query, args=(), as_dict=False):
112112
:param as_dict: If as_dict is set to True, the returned cursor objects returns
113113
query results as dictionary.
114114
"""
115+
115116
cursor = client.cursors.DictCursor if as_dict else client.cursors.Cursor
116117
cur = self._conn.cursor(cursor=cursor)
117118

118-
# Log the query
119119
try:
120+
# Log the query
120121
logger.debug("Executing SQL:" + query[0:300])
121-
cur.execute(query, args)
122+
# suppress all warnings arising from underlying SQL library
123+
with warnings.catch_warnings():
124+
warnings.simplefilter("ignore")
125+
cur.execute(query, args)
122126
except err.OperationalError as e:
123127
if 'MySQL server has gone away' in str(e) and config['database.reconnect']:
124128
warnings.warn('''Mysql server has gone away.

datajoint/fetch.py

Lines changed: 96 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .blob import unpack
77
from . import DataJointError
88
from . import key as PRIMARY_KEY
9+
import warnings
910

1011

1112
def update_dict(d1, d2):
@@ -25,9 +26,13 @@ def __init__(self, arg):
2526

2627
def copy(self):
2728
"""
29+
DEPRECATED
30+
2831
Creates and returns a copy of this object
2932
:return: copy FetchBase derivatives
3033
"""
34+
warnings.warn('Use of `copy` on `fetch` object is deprecated', stacklevel=2)
35+
3136
return self.__class__(self)
3237

3338
def _initialize_behavior(self):
@@ -37,9 +42,15 @@ def _initialize_behavior(self):
3742
@property
3843
def squeeze(self):
3944
"""
45+
DEPRECATED
46+
4047
Changes the state of the fetch object to squeeze the returned values as much as possible.
4148
:return: a copy of the fetch object
4249
"""
50+
51+
warnings.warn('Use of `squeeze` on `fetch` object is deprecated. Please use `squeeze=True` keyword arguments '
52+
'in the call to `fetch`/`keys` instead', stacklevel=2)
53+
4354
ret = self.copy()
4455
ret.ext_behavior['squeeze'] = True
4556
return ret
@@ -60,6 +71,9 @@ def _prepare_attributes(item):
6071
raise DataJointError("Index must be a sequence or a string.")
6172
return item, attributes
6273

74+
def __len__(self):
75+
return len(self._relation)
76+
6377

6478

6579

@@ -76,6 +90,8 @@ def _initialize_behavior(self):
7690

7791
def order_by(self, *args):
7892
"""
93+
DEPRECATED
94+
7995
Changes the state of the fetch object to order the results by a particular attribute.
8096
The commands are handed down to mysql.
8197
:param args: the attributes to sort by. If DESC is passed after the name, then the order is descending.
@@ -85,6 +101,8 @@ def order_by(self, *args):
85101
>>> my_relation.fetch.order_by('language', 'name DESC')
86102
87103
"""
104+
warnings.warn('Use of `order_by` on `fetch` object is deprecated. Please use `order_by` keyword arguments in '
105+
'the call to `fetch`/`keys` instead', stacklevel=2)
88106
self = Fetch(self)
89107
if len(args) > 0:
90108
self.sql_behavior['order_by'] = args
@@ -93,71 +111,103 @@ def order_by(self, *args):
93111
@property
94112
def as_dict(self):
95113
"""
114+
DEPRECATED
115+
96116
Changes the state of the fetch object to return dictionaries.
97117
:return: a copy of the fetch object
98118
Example:
99119
100120
>>> my_relation.fetch.as_dict()
101121
102122
"""
123+
warnings.warn('Use of `as_dict` on `fetch` object is deprecated. Please use `as_dict` keyword arguments in the '
124+
'call to `fetch`/`keys` instead', stacklevel=2)
103125
ret = Fetch(self)
104126
ret.sql_behavior['as_dict'] = True
105127
return ret
106128

107129
def limit(self, limit):
108130
"""
131+
DEPRECATED
132+
109133
Limits the number of items fetched.
110134
111135
:param limit: limit on the number of items
112136
:return: a copy of the fetch object
113137
"""
138+
warnings.warn('Use of `limit` on `fetch` object is deprecated. Please use `limit` keyword arguments in '
139+
'the call to `fetch`/`keys` instead', stacklevel=2)
114140
ret = Fetch(self)
115141
ret.sql_behavior['limit'] = limit
116142
return ret
117143

118144
def offset(self, offset):
119145
"""
146+
DEPRECATED
147+
120148
Offsets the number of itms fetched. Needs to be applied with limit.
121149
122150
:param offset: offset
123151
:return: a copy of the fetch object
124152
"""
153+
154+
warnings.warn('Use of `offset` on `fetch` object is deprecated. Please use `offset` keyword arguments in '
155+
'the call to `fetch`/`keys` instead', stacklevel=2)
125156
ret = Fetch(self)
126157
if ret.sql_behavior['limit'] is None:
127158
warnings.warn('You should supply a limit together with an offset,')
128159
ret.sql_behavior['offset'] = offset
129160
return ret
130161

131-
def __call__(self, **kwargs):
162+
def __call__(self, *attrs, **kwargs):
132163
"""
133164
Fetches the relation from the database table into an np.array and unpacks blob attributes.
134165
166+
:param attrs: OPTIONAL. one or more attributes to fetch. If not provided, the call will return
167+
all attributes of this relation. If provided, returns tuples with an entry for each attribute.
135168
:param offset: the number of tuples to skip in the returned result
136169
:param limit: the maximum number of tuples to return
137170
:param order_by: the list of attributes to order the results. No ordering should be assumed if order_by=None.
138171
:param as_dict: returns a list of dictionaries instead of a record array
139172
:return: the contents of the relation in the form of a structured numpy.array
140173
"""
174+
# if 'order_by' passed in a string, make into list
175+
if isinstance(kwargs.get('order_by'), str):
176+
kwargs['order_by'] = [kwargs['order_by']]
177+
141178
sql_behavior = update_dict(self.sql_behavior, kwargs)
142179
ext_behavior = update_dict(self.ext_behavior, kwargs)
180+
total_behavior = dict(sql_behavior)
181+
total_behavior.update(ext_behavior)
143182

144183
unpack_ = partial(unpack, squeeze=ext_behavior['squeeze'])
145184

146185
if sql_behavior['limit'] is None and sql_behavior['offset'] is not None:
147186
warnings.warn('Offset set, but no limit. Setting limit to a large number. '
148187
'Consider setting a limit explicitly.')
149188
sql_behavior['limit'] = 2 * len(self._relation)
150-
cur = self._relation.cursor(**sql_behavior)
151-
heading = self._relation.heading
152-
if sql_behavior['as_dict']:
153-
ret = [OrderedDict((name, unpack_(d[name]) if heading[name].is_blob else d[name])
154-
for name in heading.names)
155-
for d in cur.fetchall()]
156-
else:
157-
ret = list(cur.fetchall())
158-
ret = np.array(ret, dtype=heading.as_dtype)
159-
for blob_name in heading.blobs:
160-
ret[blob_name] = list(map(unpack_, ret[blob_name]))
189+
190+
if len(attrs) == 0: # fetch all attributes
191+
cur = self._relation.cursor(**sql_behavior)
192+
heading = self._relation.heading
193+
if sql_behavior['as_dict']:
194+
ret = [OrderedDict((name, unpack_(d[name]) if heading[name].is_blob else d[name])
195+
for name in heading.names)
196+
for d in cur.fetchall()]
197+
else:
198+
ret = list(cur.fetchall())
199+
ret = np.array(ret, dtype=heading.as_dtype)
200+
for blob_name in heading.blobs:
201+
ret[blob_name] = list(map(unpack_, ret[blob_name]))
202+
203+
else: # if list of attributes provided
204+
attributes = [a for a in attrs if a is not PRIMARY_KEY]
205+
result = self._relation.proj(*attributes).fetch(**total_behavior)
206+
return_values = [
207+
list(to_dicts(result[self._relation.primary_key]))
208+
if attribute is PRIMARY_KEY else result[attribute]
209+
for attribute in attrs]
210+
ret = return_values[0] if len(attrs) == 1 else return_values
161211

162212
return ret
163213

@@ -193,6 +243,8 @@ def keys(self, **kwargs):
193243

194244
def __getitem__(self, item):
195245
"""
246+
DEPRECATED
247+
196248
Fetch attributes as separate outputs.
197249
datajoint.key is a special value that requests the entire primary key
198250
:return: tuple with an entry for each element of item
@@ -201,6 +253,10 @@ def __getitem__(self, item):
201253
>>> a, b = relation['a', 'b']
202254
>>> a, b, key = relation['a', 'b', datajoint.key]
203255
"""
256+
257+
warnings.warn('Use of `rel.fetch[a, b]` notation is deprecated. Please use `rel.fetch(a, b) for equivalent '
258+
'result', stacklevel=2)
259+
204260
behavior = dict(self.sql_behavior)
205261
behavior.update(self.ext_behavior)
206262

@@ -222,8 +278,7 @@ def __repr__(self):
222278
["\t{key}:\t{value}".format(key=k, value=str(v)) for k, v in behavior.items() if v is not None])
223279
return repr_str
224280

225-
def __len__(self):
226-
return len(self._relation)
281+
227282

228283

229284
class Fetch1(FetchBase, Callable):
@@ -233,28 +288,43 @@ class Fetch1(FetchBase, Callable):
233288
:param relation: relation the fetch object fetches data from
234289
"""
235290

236-
def __call__(self, **kwargs):
291+
def __call__(self, *attrs, **kwargs):
237292
"""
238293
This version of fetch is called when self is expected to contain exactly one tuple.
239294
:return: the one tuple in the relation in the form of a dict
240295
"""
241296
heading = self._relation.heading
242297

243-
#sql_behavior = update_dict(self.sql_behavior, kwargs)
298+
# sql_behavior = update_dict(self.sql_behavior, kwargs)
244299
ext_behavior = update_dict(self.ext_behavior, kwargs)
245300

246301
unpack_ = partial(unpack, squeeze=ext_behavior['squeeze'])
247302

303+
if len(attrs) == 0: # fetch all attributes
304+
cur = self._relation.cursor(as_dict=True)
305+
ret = cur.fetchone()
306+
if not ret or cur.fetchone():
307+
raise DataJointError('fetch1 should only be used for relations with exactly one tuple')
308+
ret = OrderedDict((name, unpack_(ret[name]) if heading[name].is_blob else ret[name])
309+
for name in heading.names)
310+
else:
311+
attributes = [a for a in attrs if a is not PRIMARY_KEY]
312+
result = self._relation.proj(*attributes).fetch(**ext_behavior)
313+
if len(result) != 1:
314+
raise DataJointError('fetch1 should only return one tuple. %d tuples were found' % len(result))
315+
return_values = tuple(
316+
next(to_dicts(result[self._relation.primary_key]))
317+
if attribute is PRIMARY_KEY else result[attribute][0]
318+
for attribute in attrs)
319+
ret = return_values[0] if len(attrs) == 1 else return_values
320+
248321

249-
cur = self._relation.cursor(as_dict=True)
250-
ret = cur.fetchone()
251-
if not ret or cur.fetchone():
252-
raise DataJointError('fetch1 should only be used for relations with exactly one tuple')
253-
return OrderedDict((name, unpack_(ret[name]) if heading[name].is_blob else ret[name])
254-
for name in heading.names)
322+
return ret
255323

256324
def __getitem__(self, item):
257325
"""
326+
DEPRECATED
327+
258328
Fetch attributes as separate outputs.
259329
datajoint.key is a special value that requests the entire primary key
260330
:return: tuple with an entry for each element of item
@@ -265,10 +335,13 @@ def __getitem__(self, item):
265335
>>> a, b, key = relation['a', 'b', datajoint.key]
266336
267337
"""
338+
warnings.warn('Use of `rel.fetch[a, b]` notation is deprecated. Please use `rel.fetch(a, b) for equivalent '
339+
'result', stacklevel=2)
340+
268341
behavior = dict(self.sql_behavior)
269342
behavior.update(self.ext_behavior)
270343

271-
single_output = isinstance(item, str) or item is PRIMARY_KEY or isinstance(item, int)
344+
single_output = isinstance(item, str) or item is PRIMARY_KEY
272345
item, attributes = self._prepare_attributes(item)
273346
result = self._relation.proj(*attributes).fetch(**behavior)
274347
if len(result) != 1:

datajoint/relational_operand.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ def __len__(self):
457457
"""
458458
number of tuples in the relation.
459459
"""
460-
return U().aggr(self, n='count(*)').fetch1['n']
460+
return U().aggr(self, n='count(*)').fetch1('n')
461461

462462
def __bool__(self):
463463
"""

datajoint/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.6.1"
1+
__version__ = "0.7.0"

0 commit comments

Comments
 (0)