Skip to content

Commit 33be384

Browse files
Merge branch 'master' into issue151
2 parents 54d4e46 + 7b43a51 commit 33be384

17 files changed

+151
-22
lines changed

CHANGELOG.md

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

33
### 0.13.3 -- TBD
4+
* Bugfix - Fix Python 3.10 compatibility (#983) PR #972
5+
* Bugfix - Allow renaming non-conforming attributes in proj (#982) PR #972
6+
* Add - Expose proxy feature for S3 external stores (#961) PR #962
47
* Bugfix - Dependencies not properly loaded on populate. (#902) PR #919
58
* Bugfix - Replace use of numpy aliases of built-in types with built-in type. (#938) PR #939
69
* Bugfix - Deletes and drops must include the master of each part. (#151 and #374) PR #957
710
* Bugfix - `ExternalTable.delete` should not remove row on error (#953) PR #956
811
* Bugfix - Fix error handling of remove_object function in `s3.py` (#952) PR #955
12+
* Bugfix - Fix regression issue with `DISTINCT` clause and `GROUP_BY` (#914) PR #963
13+
* Bugfix - Fix sql code generation to comply with sql mode `ONLY_FULL_GROUP_BY` (#916) PR #965
14+
* Bugfix - Fix count for left-joined `QueryExpressions` (#951) PR #966
15+
* Bugfix - Fix assertion error when performing a union into a join (#930) PR #967
916

1017
### 0.13.2 -- May 7, 2021
1118
* Update `setuptools_certificate` dependency to new name `otumat`

LNX-docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ services:
3232
interval: 1s
3333
fakeservices.datajoint.io:
3434
<<: *net
35-
image: datajoint/nginx:v0.0.18
35+
image: datajoint/nginx:v0.0.19
3636
environment:
3737
- ADD_db_TYPE=DATABASE
3838
- ADD_db_ENDPOINT=db:3306

datajoint/declare.py

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

1212
UUID_DATA_TYPE = 'binary(16)'
1313
MAX_TABLE_NAME_LENGTH = 64
14-
CONSTANT_LITERALS = {'CURRENT_TIMESTAMP'} # SQL literals to be used without quotes (case insensitive)
14+
CONSTANT_LITERALS = {'CURRENT_TIMESTAMP', 'NULL'} # SQL literals to be used without quotes (case insensitive)
1515
EXTERNAL_TABLE_ROOT = '~external'
1616

1717
TYPE_PATTERN = {k: re.compile(v, re.I) for k, v in dict(

datajoint/diagram.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def make_dot(self):
296296
node.set_style('filled')
297297

298298
for edge in dot.get_edges():
299-
# see http://www.graphviz.org/content/attrs
299+
# see https://graphviz.org/doc/info/attrs.html
300300
src = edge.get_source().strip('"')
301301
dest = edge.get_destination().strip('"')
302302
props = graph.get_edge_data(src, dest)

datajoint/expression.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .preview import preview, repr_html
1010
from .condition import AndList, Not, \
1111
make_condition, assert_join_compatibility, extract_column_names, PromiscuousOperand
12+
from .declare import CONSTANT_LITERALS
1213

1314
logger = logging.getLogger(__name__)
1415

@@ -44,6 +45,9 @@ class QueryExpression:
4445
_heading = None
4546
_support = None
4647

48+
# If the query will be using distinct
49+
_distinct = False
50+
4751
@property
4852
def connection(self):
4953
""" a dj.Connection object """
@@ -106,9 +110,8 @@ def make_sql(self, fields=None):
106110
Make the SQL SELECT statement.
107111
:param fields: used to explicitly set the select attributes
108112
"""
109-
distinct = self.heading.names == self.primary_key
110113
return 'SELECT {distinct}{fields} FROM {from_}{where}'.format(
111-
distinct="DISTINCT " if distinct else "",
114+
distinct="DISTINCT " if self._distinct else "",
112115
fields=self.heading.as_sql(fields or self.heading.names),
113116
from_=self.from_clause(), where=self.where_clause())
114117

@@ -266,9 +269,11 @@ def join(self, other, semantic_check=True, left=False):
266269
- join_attributes)
267270
# need subquery if any of the join attributes are derived
268271
need_subquery1 = (need_subquery1 or isinstance(self, Aggregation) or
269-
any(n in self.heading.new_attributes for n in join_attributes))
272+
any(n in self.heading.new_attributes for n in join_attributes)
273+
or isinstance(self, Union))
270274
need_subquery2 = (need_subquery2 or isinstance(other, Aggregation) or
271-
any(n in other.heading.new_attributes for n in join_attributes))
275+
any(n in other.heading.new_attributes for n in join_attributes)
276+
or isinstance(self, Union))
272277
if need_subquery1:
273278
self = self.make_subquery()
274279
if need_subquery2:
@@ -309,9 +314,9 @@ def proj(self, *attributes, **named_attributes):
309314
Each attribute name can only be used once.
310315
"""
311316
# new attributes in parentheses are included again with the new name without removing original
312-
duplication_pattern = re.compile(r'\s*\(\s*(?P<name>[a-z][a-z_0-9]*)\s*\)\s*$')
317+
duplication_pattern = re.compile(fr'^\s*\(\s*(?!{"|".join(CONSTANT_LITERALS)})(?P<name>[a-zA-Z_]\w*)\s*\)\s*$')
313318
# attributes without parentheses renamed
314-
rename_pattern = re.compile(r'\s*(?P<name>[a-z][a-z_0-9]*)\s*$')
319+
rename_pattern = re.compile(fr'^\s*(?!{"|".join(CONSTANT_LITERALS)})(?P<name>[a-zA-Z_]\w*)\s*$')
315320
replicate_map = {k: m.group('name')
316321
for k, m in ((k, duplication_pattern.match(v)) for k, v in named_attributes.items()) if m}
317322
rename_map = {k: m.group('name')
@@ -440,8 +445,10 @@ def tail(self, limit=25, **fetch_kwargs):
440445
def __len__(self):
441446
""":return: number of elements in the result set e.g. ``len(q1)``."""
442447
return self.connection.query(
443-
'SELECT count(DISTINCT {fields}) FROM {from_}{where}'.format(
444-
fields=self.heading.as_sql(self.primary_key, include_aliases=False),
448+
'SELECT {select_} FROM {from_}{where}'.format(
449+
select_=('count(*)' if any(self._left)
450+
else 'count(DISTINCT {fields})'.format(fields=self.heading.as_sql(
451+
self.primary_key, include_aliases=False))),
445452
from_=self.from_clause(),
446453
where=self.where_clause())).fetchone()[0]
447454

@@ -554,7 +561,7 @@ def create(cls, arg, group, keep_all_rows=False):
554561
if inspect.isclass(group) and issubclass(group, QueryExpression):
555562
group = group() # instantiate if a class
556563
assert isinstance(group, QueryExpression)
557-
if keep_all_rows and len(group.support) > 1:
564+
if keep_all_rows and len(group.support) > 1 or group.heading.new_attributes:
558565
group = group.make_subquery() # subquery if left joining a join
559566
join = arg.join(group, left=keep_all_rows) # reuse the join logic
560567
result = cls()
@@ -718,6 +725,7 @@ def __and__(self, other):
718725
if not isinstance(other, QueryExpression):
719726
raise DataJointError('Set U can only be restricted with a QueryExpression.')
720727
result = copy.copy(other)
728+
result._distinct = True
721729
result._heading = result.heading.set_primary_key(self.primary_key)
722730
result = result.proj()
723731
return result

datajoint/external.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pathlib import Path, PurePosixPath, PureWindowsPath
2-
from collections import Mapping
2+
from collections.abc import Mapping
33
from tqdm import tqdm
44
from .settings import config
55
from .errors import DataJointError, MissingExternalFile

datajoint/s3.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
from io import BytesIO
55
import minio # https://docs.minio.io/docs/python-client-api-reference
6+
import urllib3
67
import warnings
78
import uuid
89
import logging
@@ -16,9 +17,24 @@ class Folder:
1617
"""
1718
A Folder instance manipulates a flat folder of objects within an S3-compatible object store
1819
"""
19-
def __init__(self, endpoint, bucket, access_key, secret_key, *, secure=False, **_):
20-
self.client = minio.Minio(endpoint, access_key=access_key, secret_key=secret_key,
21-
secure=secure)
20+
def __init__(self, endpoint, bucket, access_key, secret_key, *, secure=False,
21+
proxy_server=None, **_):
22+
# from https://docs.min.io/docs/python-client-api-reference
23+
self.client = minio.Minio(
24+
endpoint,
25+
access_key=access_key,
26+
secret_key=secret_key,
27+
secure=secure,
28+
http_client=(
29+
urllib3.ProxyManager(proxy_server,
30+
timeout=urllib3.Timeout.DEFAULT_TIMEOUT,
31+
cert_reqs="CERT_REQUIRED",
32+
retries=urllib3.Retry(total=5,
33+
backoff_factor=0.2,
34+
status_forcelist=[500, 502, 503,
35+
504]))
36+
if proxy_server else None),
37+
)
2238
self.bucket = bucket
2339
if not self.client.bucket_exists(bucket):
2440
raise errors.BucketInaccessible('Inaccessible s3 bucket %s' % bucket)

datajoint/settings.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
}
5959

6060

61-
class Config(collections.MutableMapping):
61+
class Config(collections.abc.MutableMapping):
6262

6363
instance = None
6464

@@ -137,7 +137,8 @@ def get_store_spec(self, store):
137137
spec['subfolding'] = spec.get('subfolding', DEFAULT_SUBFOLDING)
138138
spec_keys = { # REQUIRED in uppercase and allowed in lowercase
139139
'file': ('PROTOCOL', 'LOCATION', 'subfolding', 'stage'),
140-
's3': ('PROTOCOL', 'ENDPOINT', 'BUCKET', 'ACCESS_KEY', 'SECRET_KEY', 'LOCATION', 'secure', 'subfolding', 'stage')}
140+
's3': ('PROTOCOL', 'ENDPOINT', 'BUCKET', 'ACCESS_KEY', 'SECRET_KEY', 'LOCATION',
141+
'secure', 'subfolding', 'stage', 'proxy_server')}
141142

142143
try:
143144
spec_keys = spec_keys[spec.get('protocol', '').lower()]

docs-parts/intro/Releases_lang1.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
0.13.3 -- TBD
22
----------------------
3+
* Bugfix - Fix Python 3.10 compatibility (#983) PR #972
4+
* Bugfix - Allow renaming non-conforming attributes in proj (#982) PR #972
5+
* Add - Expose proxy feature for S3 external stores (#961) PR #962
36
* Bugfix - Dependencies not properly loaded on populate. (#902) PR #919
47
* Bugfix - Replace use of numpy aliases of built-in types with built-in type. (#938) PR #939
58
* Bugfix - Deletes and drops must include the master of each part. (#151 and #374) PR #957
69
* Bugfix - `ExternalTable.delete` should not remove row on error (#953) PR #956
710
* Bugfix - Fix error handling of remove_object function in `s3.py` (#952) PR #955
11+
* Bugfix - Fix sql code generation to comply with sql mode ``ONLY_FULL_GROUP_BY`` (#916) PR #965
12+
* Bugfix - Fix count for left-joined ``QueryExpressions`` (#951) PR #966
13+
* Bugfix - Fix assertion error when performing a union into a join (#930) PR #967
14+
* Bugfix - Fix regression issue with `DISTINCT` clause and `GROUP_BY` (#914) PR #963
815

916
0.13.2 -- May 7, 2021
1017
----------------------

local-docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ services:
3434
interval: 1s
3535
fakeservices.datajoint.io:
3636
<<: *net
37-
image: datajoint/nginx:v0.0.18
37+
image: datajoint/nginx:v0.0.19
3838
environment:
3939
- ADD_db_TYPE=DATABASE
4040
- ADD_db_ENDPOINT=db:3306

0 commit comments

Comments
 (0)