Skip to content

Commit 0409ffd

Browse files
Merge branch 'datajoint:master' into r013-docs
2 parents e45e922 + 7764467 commit 0409ffd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+812
-212
lines changed

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ branch = False
33
source = datajoint
44

55
[report]
6+
show_missing = True

.github/workflows/development.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
matrix:
1717
py_ver: ["3.8"]
18-
mysql_ver: ["8.0", "5.7", "5.6"]
18+
mysql_ver: ["8.0", "5.7"]
1919
include:
2020
- py_ver: "3.7"
2121
mysql_ver: "5.7"
@@ -36,11 +36,11 @@ jobs:
3636
- name: Run primary tests
3737
env:
3838
UID: "1001"
39-
GID: "116"
39+
GID: "121"
4040
PY_VER: ${{matrix.py_ver}}
4141
MYSQL_VER: ${{matrix.mysql_ver}}
4242
ALPINE_VER: "3.10"
43-
MINIO_VER: RELEASE.2019-09-26T19-42-35Z
43+
MINIO_VER: RELEASE.2021-09-03T03-56-13Z
4444
COMPOSE_HTTP_TIMEOUT: "120"
4545
COVERALLS_SERVICE_NAME: travis-ci
4646
COVERALLS_REPO_TOKEN: fd0BoXG46TPReEem0uMy7BJO5j0w1MQiY

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ notebook
2424
.vscode
2525
__main__.py
2626
jupyter_custom.js
27-
apk_requirements.txt
27+
apk_requirements.txt
28+
.eggs

CHANGELOG.md

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,59 @@
11
## Release notes
22

3-
### 0.13.0 -- Feb 15, 2021
4-
* Re-implement query transpilation into SQL, fixing issues (#386, #449, #450, #484). PR #754
5-
* Re-implement cascading deletes for better performance. PR #839.
6-
* Add table method `.update1` to update a row in the table with new values PR #763
7-
* Python datatypes are now enabled by default in blobs (#761). PR #785
3+
### 0.13.3 -- TBD
4+
* Bugfix - Dependencies not properly loaded on populate. (#902) PR #919
5+
* Bugfix - Replace use of numpy aliases of built-in types with built-in type. (#938) PR #939
6+
* Bugfix - `ExternalTable.delete` should not remove row on error (#953) PR #956
7+
* Bugfix - Fix error handling of remove_object function in `s3.py` (#952) PR #955
8+
9+
### 0.13.2 -- May 7, 2021
10+
* Update `setuptools_certificate` dependency to new name `otumat`
11+
* Bugfix - Explicit calls to `dj.Connection` throw error due to missing `host_input` (#895) PR #907
12+
* Bugfix - Correct count of deleted items. (#897) PR #912
13+
14+
### 0.13.1 -- Apr 16, 2021
15+
* Add `None` as an alias for `IS NULL` comparison in `dict` restrictions (#824) PR #893
16+
* Drop support for MySQL 5.6 since it has reached EOL PR #893
17+
* Bugfix - `schema.list_tables()` is not topologically sorted (#838) PR #893
18+
* Bugfix - Diagram part tables do not show proper class name (#882) PR #893
19+
* Bugfix - Error in complex restrictions (#892) PR #893
20+
* Bugfix - WHERE and GROUP BY clases are dropped on joins with aggregation (#898, #899) PR #893
21+
22+
### 0.13.0 -- Mar 24, 2021
23+
* Re-implement query transpilation into SQL, fixing issues (#386, #449, #450, #484, #558). PR #754
24+
* Re-implement cascading deletes for better performance. PR #839
25+
* Add support for deferred schema activation to allow for greater modularity. (#834) PR #839
26+
* Add query caching mechanism for offline development (#550) PR #839
27+
* Add table method `.update1` to update a row in the table with new values (#867) PR #763, #889
28+
* Python datatypes are now enabled by default in blobs (#761). PR #859
829
* Added permissive join and restriction operators `@` and `^` (#785) PR #754
930
* Support DataJoint datatype and connection plugins (#715, #729) PR 730, #735
10-
* add `dj.key_hash` alias to `dj.hash.key_hash`
11-
* default enable_python_native_blobs to True
12-
* Drop support for Python 3.5
31+
* Add `dj.key_hash` alias to `dj.hash.key_hash` (#804) PR #862
32+
* Default enable_python_native_blobs to True
33+
* Bugfix - Regression error on joins with same attribute name (#857) PR #878
34+
* Bugfix - Error when `fetch1('KEY')` when `dj.config['fetch_format']='frame'` set (#876) PR #880, #878
35+
* Bugfix - Error when cascading deletes in tables with many, complex keys (#883, #886) PR #839
36+
* Add deprecation warning for `_update`. PR #889
37+
* Add `purge_query_cache` utility. PR #889
38+
* Add tests for query caching and permissive join and restriction. PR #889
39+
* Drop support for Python 3.5 (#829) PR #861
40+
41+
### 0.12.9 -- Mar 12, 2021
42+
* Fix bug with fetch1 with `dj.config['fetch_format']="frame"`. (#876) PR #880
1343

1444
### 0.12.8 -- Jan 12, 2021
1545
* table.children, .parents, .descendents, and ancestors can return queryable objects. PR #833
1646
* Load dependencies before querying dependencies. (#179) PR #833
1747
* Fix display of part tables in `schema.save`. (#821) PR #833
1848
* Add `schema.list_tables`. (#838) PR #844
1949
* Fix minio new version regression. PR #847
20-
* Add more S3 logging for debugging. (#831) PR #832
50+
* Add more S3 logging for debugging. (#831) PR #832
2151
* Convert testing framework from TravisCI to GitHub Actions (#841) PR #840
22-
52+
2353
### 0.12.7 -- Oct 27, 2020
2454
* Fix case sensitivity issues to adapt to MySQL 8+. PR #819
2555
* Fix pymysql regression bug (#814) PR #816
26-
* Adapted attribute types now have dtype=object in all recarray results. PR #811
56+
* Adapted attribute types now have dtype=object in all recarray results. PR #811
2757

2858
### 0.12.6 -- May 15, 2020
2959
* Add `order_by` to `dj.kill` (#668, #779) PR #775, #783
@@ -115,9 +145,9 @@
115145
* Bugfix in restriction of the form (A & B) * B (#463)
116146
* Improved error messages (#466)
117147

118-
### 0.10.0 -- Jan 10, 2018
148+
### 0.10.0 -- Jan 10, 2018
119149
* Deletes are more efficient (#424)
120-
* ERD shows table definition on tooltip hover in Jupyter (#422)
150+
* ERD shows table definition on tooltip hover in Jupyter (#422)
121151
* S3 external storage
122152
* Garbage collection for external sorage
123153
* Most operators and methods of tables can be invoked as class methods rather than instance methods (#407)
@@ -131,7 +161,7 @@
131161
* Implement union operator +
132162
* Implement file-based external storage
133163

134-
### 0.8.0 -- Jul 26, 2017
164+
### 0.8.0 -- Jul 26, 2017
135165
Documentation and tutorials available at https://docs.datajoint.io and https://tutorials.datajoint.io
136166
* improved the ERD graphics and features using the graphviz libraries (#207, #333)
137167
* improved password handling logic (#322, #321)
@@ -150,11 +180,11 @@ Documentation and tutorials available at https://docs.datajoint.io and https://t
150180
* Added `dj.create_virtual_module`
151181

152182
### 0.4.10 (#286) -- Feb 6, 2017
153-
* Removed Vagrant and Readthedocs support
183+
* Removed Vagrant and Readthedocs support
154184
* Explicit saving of configuration (issue #284)
155185

156186
### 0.4.9 (#285) -- Feb 2, 2017
157-
* Fixed setup.py for pip install
187+
* Fixed setup.py for pip install
158188

159189
### 0.4.7 (#281) -- Jan 24, 2017
160190
* Fixed issues related to order of attributes in projection.
@@ -183,10 +213,10 @@ Documentation and tutorials available at https://docs.datajoint.io and https://t
183213

184214
### 0.3.8 -- Aug 2, 2016
185215
* added the `_update` method in `base_relation`. It allows updating values in existing tuples.
186-
* bugfix in reading values of type double. Previously it was cast as float32.
216+
* bugfix in reading values of type double. Previously it was cast as float32.
187217

188218
### 0.3.7 -- Jul 31, 2016
189-
* added parameter `ignore_extra_fields` in `insert`
219+
* added parameter `ignore_extra_fields` in `insert`
190220
* `insert(..., skip_duplicates=True)` now relies on `SELECT IGNORE`. Previously it explicitly checked if tuple already exists.
191221
* table previews now include blob attributes displaying the string <BLOB>
192222

LNX-docker-compose.yml

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --build --exit-code-from app
1+
# docker-compose -f LNX-docker-compose.yml --env-file LNX.env up --exit-code-from app --build
22
version: '2.2'
33
x-net: &net
44
networks:
@@ -32,7 +32,7 @@ services:
3232
interval: 1s
3333
fakeservices.datajoint.io:
3434
<<: *net
35-
image: raphaelguzman/nginx:v0.0.13
35+
image: datajoint/nginx:v0.0.18
3636
environment:
3737
- ADD_db_TYPE=DATABASE
3838
- ADD_db_ENDPOINT=db:3306
@@ -72,15 +72,17 @@ services:
7272
- COVERALLS_SERVICE_NAME
7373
- COVERALLS_REPO_TOKEN
7474
working_dir: /src
75-
command: >
76-
/bin/sh -c
77-
"
78-
pip install --user -r test_requirements.txt;
79-
pip install --user .;
80-
pip freeze | grep datajoint;
81-
nosetests -vsw tests --with-coverage --cover-package=datajoint && coveralls;
82-
# jupyter notebook;
83-
"
75+
command:
76+
- sh
77+
- -c
78+
- |
79+
set -e
80+
pip install --user -r test_requirements.txt
81+
pip install -e .
82+
pip freeze | grep datajoint
83+
nosetests -vsw tests --with-coverage --cover-package=datajoint
84+
coveralls
85+
# jupyter notebook
8486
# ports:
8587
# - "8888:8888"
8688
user: ${UID}:${GID}

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ A number of labs are currently adopting DataJoint and we are quickly getting the
108108
PY_VER=3.7
109109
ALPINE_VER=3.10
110110
MYSQL_VER=5.7
111-
MINIO_VER=RELEASE.2019-09-26T19-42-35Z
111+
MINIO_VER=RELEASE.2021-09-03T03-56-13Z
112112
UID=1000
113113
GID=1000
114114
```
@@ -136,6 +136,8 @@ GID=1000
136136
* Add entry in `/etc/hosts` for `127.0.0.1 fakeservices.datajoint.io`
137137

138138

139+
140+
139141
### Launch Jupyter Notebook for Interactive Use
140142
* Navigate to `localhost:8888`
141143
* Input Jupyter password

datajoint/autopopulate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def _rename_attributes(table, props):
3939
if self._key_source is None:
4040
parents = self.target.parents(primary=True, as_objects=True, foreign_key_info=True)
4141
if not parents:
42-
raise DataJointError(
43-
'A relation must have primary dependencies for auto-populate to work')
42+
raise DataJointError('A table must have dependencies '
43+
'from its primary key for auto-populate to work')
4444
self._key_source = _rename_attributes(*parents[0])
4545
for q in parents[1:]:
4646
self._key_source *= _rename_attributes(*q)

datajoint/blob.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def pack_blob(self, obj):
164164
return self.pack_recarray(np.array(obj))
165165
if isinstance(obj, np.number):
166166
return self.pack_array(np.array(obj))
167-
if isinstance(obj, (np.bool, np.bool_)):
167+
if isinstance(obj, (bool, np.bool_)):
168168
return self.pack_array(np.array(obj))
169169
if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
170170
return self.pack_datetime(obj)
@@ -365,7 +365,7 @@ def read_struct(self):
365365
raw_data = [
366366
tuple(self.read_blob(n_bytes=int(self.read_value('uint64'))) for _ in range(n_fields))
367367
for __ in range(n_elem)]
368-
data = np.array(raw_data, dtype=list(zip(field_names, repeat(np.object))))
368+
data = np.array(raw_data, dtype=list(zip(field_names, repeat(object))))
369369
return self.squeeze(data.reshape(shape, order="F"), convert_to_scalar=False).view(MatStruct)
370370

371371
def pack_struct(self, array):

datajoint/condition.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ def __init__(self, operand):
2121

2222
class AndList(list):
2323
"""
24-
A list of conditions to by applied to a query expression by logical conjunction: the conditions are AND-ed.
25-
All other collections (lists, sets, other entity sets, etc) are applied by logical disjunction (OR).
24+
A list of conditions to by applied to a query expression by logical conjunction: the
25+
conditions are AND-ed. All other collections (lists, sets, other entity sets, etc) are
26+
applied by logical disjunction (OR).
2627
2728
Example:
2829
expr2 = expr & dj.AndList((cond1, cond2, cond3))
@@ -49,14 +50,16 @@ def assert_join_compatibility(expr1, expr2):
4950
the matching attributes in the two expressions must be in the primary key of one or the
5051
other expression.
5152
Raises an exception if not compatible.
53+
5254
:param expr1: A QueryExpression object
5355
:param expr2: A QueryExpression object
5456
"""
5557
from .expression import QueryExpression, U
5658

5759
for rel in (expr1, expr2):
5860
if not isinstance(rel, (U, QueryExpression)):
59-
raise DataJointError('Object %r is not a QueryExpression and cannot be joined.' % rel)
61+
raise DataJointError(
62+
'Object %r is not a QueryExpression and cannot be joined.' % rel)
6063
if not isinstance(expr1, U) and not isinstance(expr2, U): # dj.U is always compatible
6164
try:
6265
raise DataJointError(
@@ -70,9 +73,11 @@ def assert_join_compatibility(expr1, expr2):
7073
def make_condition(query_expression, condition, columns):
7174
"""
7275
Translate the input condition into the equivalent SQL condition (a string)
76+
7377
:param query_expression: a dj.QueryExpression object to apply condition
7478
:param condition: any valid restriction object.
75-
:param columns: a set passed by reference to collect all column names used in the condition.
79+
:param columns: a set passed by reference to collect all column names used in the
80+
condition.
7681
:return: an SQL condition string or a boolean value.
7782
"""
7883
from .expression import QueryExpression, Aggregation, U
@@ -84,7 +89,8 @@ def prep_value(k, v):
8489
try:
8590
v = uuid.UUID(v)
8691
except (AttributeError, ValueError):
87-
raise DataJointError('Badly formed UUID {v} in restriction by `{k}`'.format(k=k, v=v)) from None
92+
raise DataJointError(
93+
'Badly formed UUID {v} in restriction by `{k}`'.format(k=k, v=v))
8894
return "X'%s'" % v.bytes.hex()
8995
if isinstance(v, (datetime.date, datetime.datetime, datetime.time, decimal.Decimal)):
9096
return '"%s"' % v
@@ -101,12 +107,13 @@ def prep_value(k, v):
101107
# restrict by string
102108
if isinstance(condition, str):
103109
columns.update(extract_column_names(condition))
104-
return template % condition.strip().replace("%", "%%") # escape % in strings, see issue #376
110+
return template % condition.strip().replace("%", "%%") # escape %, see issue #376
105111

106112
# restrict by AndList
107113
if isinstance(condition, AndList):
108114
# omit all conditions that evaluate to True
109-
items = [item for item in (make_condition(query_expression, cond, columns) for cond in condition)
115+
items = [item for item in (make_condition(query_expression, cond, columns)
116+
for cond in condition)
110117
if item is not True]
111118
if any(item is False for item in items):
112119
return negate # if any item is False, the whole thing is False
@@ -122,18 +129,21 @@ def prep_value(k, v):
122129
if isinstance(condition, bool):
123130
return negate != condition
124131

125-
# restrict by a mapping such as a dict -- convert to an AndList of string equality conditions
132+
# restrict by a mapping/dict -- convert to an AndList of string equality conditions
126133
if isinstance(condition, collections.abc.Mapping):
127134
common_attributes = set(condition).intersection(query_expression.heading.names)
128135
if not common_attributes:
129136
return not negate # no matching attributes -> evaluates to True
130137
columns.update(common_attributes)
131138
return template % ('(' + ') AND ('.join(
132-
'`%s`=%s' % (k, prep_value(k, condition[k])) for k in common_attributes) + ')')
139+
'`%s`%s' % (k, ' IS NULL' if condition[k] is None
140+
else f'={prep_value(k, condition[k])}')
141+
for k in common_attributes) + ')')
133142

134143
# restrict by a numpy record -- convert to an AndList of string equality conditions
135144
if isinstance(condition, numpy.void):
136-
common_attributes = set(condition.dtype.fields).intersection(query_expression.heading.names)
145+
common_attributes = set(condition.dtype.fields).intersection(
146+
query_expression.heading.names)
137147
if not common_attributes:
138148
return not negate # no matching attributes -> evaluate to True
139149
columns.update(common_attributes)
@@ -153,7 +163,8 @@ def prep_value(k, v):
153163
if isinstance(condition, QueryExpression):
154164
if check_compatibility:
155165
assert_join_compatibility(query_expression, condition)
156-
common_attributes = [q for q in condition.heading.names if q in query_expression.heading.names]
166+
common_attributes = [q for q in condition.heading.names
167+
if q in query_expression.heading.names]
157168
columns.update(common_attributes)
158169
if isinstance(condition, Aggregation):
159170
condition = condition.make_subquery()
@@ -175,15 +186,17 @@ def prep_value(k, v):
175186
except TypeError:
176187
raise DataJointError('Invalid restriction type %r' % condition)
177188
else:
178-
or_list = [item for item in or_list if item is not False] # ignore all False conditions
179-
if any(item is True for item in or_list): # if any item is True, the whole thing is True
189+
or_list = [item for item in or_list if item is not False] # ignore False conditions
190+
if any(item is True for item in or_list): # if any item is True, entirely True
180191
return not negate
181-
return template % ('(%s)' % ' OR '.join(or_list)) if or_list else negate # an empty or list is False
192+
return template % ('(%s)' % ' OR '.join(or_list)) if or_list else negate
182193

183194

184195
def extract_column_names(sql_expression):
185196
"""
186-
extract all presumed column names from an sql expression such as the WHERE clause, for example.
197+
extract all presumed column names from an sql expression such as the WHERE clause,
198+
for example.
199+
187200
:param sql_expression: a string containing an SQL expression
188201
:return: set of extracted column names
189202
This may be MySQL-specific for now.
@@ -205,5 +218,8 @@ def extract_column_names(sql_expression):
205218
s = re.sub(r"(\b[a-z][a-z_0-9]*)\(", "(", s)
206219
remaining_tokens = set(re.findall(r"\b[a-z][a-z_0-9]*\b", s))
207220
# update result removing reserved words
208-
result.update(remaining_tokens - {"is", "in", "between", "like", "and", "or", "null", "not"})
221+
result.update(remaining_tokens - {"is", "in", "between", "like", "and", "or", "null",
222+
"not", "interval", "second", "minute", "hour", "day",
223+
"month", "week", "year"
224+
})
209225
return result

0 commit comments

Comments
 (0)