Skip to content

Commit c127fac

Browse files
author
Chris Turner
committed
Merge remote-tracking branch 'upstream/release013' into issue-873
2 parents deb2b98 + 939ca8b commit c127fac

39 files changed

+536
-216
lines changed

.github/workflows/development.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ jobs:
2121
mysql_ver: "5.7"
2222
- py_ver: "3.6"
2323
mysql_ver: "5.7"
24-
- py_ver: "3.5"
25-
mysql_ver: "5.7"
2624
steps:
2725
- uses: actions/checkout@v2
2826
- name: Set up Python ${{matrix.py_ver}}
@@ -50,4 +48,4 @@ jobs:
5048
- name: Run style tests
5149
run: |
5250
flake8 --ignore=E121,E123,E126,E226,E24,E704,W503,W504,E722,F401,W605 datajoint \
53-
--count --max-complexity=62 --max-line-length=127 --statistics
51+
--count --max-complexity=62 --max-line-length=127 --statistics

CHANGELOG.md

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

3+
### 0.13.0 -- TBD
4+
* Support DataJoint datatype and connection plugins (#715, #729) PR 730, #735
5+
* Allow updating specified secondary attributes using `update1` PR #763
6+
* add dj.key_hash reference to dj.hash.key_hash, treat as 'public api'
7+
* default enable_python_native_blobs to True
8+
* Remove python 3.5 support
9+
310
### 0.12.8 -- Jan 12, 2021
411
* table.children, .parents, .descendents, and ancestors can return queryable objects. PR #833
512
* Load dependencies before querying dependencies. (#179) PR #833

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ Some Python datatypes such as dicts were coerced into numpy recarrays and then f
3232
However, since some Python types were coerced into MATLAB types, old blobs and new blobs may now be fetched as different types of objects even if they were inserted the same way.
3333
For example, new `dict` objects will be returned as `dict` while the same types of objects inserted with `datajoint 0.11` will be recarrays.
3434

35-
Since this is a big change, we chose to disable full blob support by default as a temporary precaution, which will be removed in version 0.13.
35+
Since this is a big change, we chose to temporarily disable this feature by default in DataJoint for Python 0.12.x, allowing users to adjust their code if necessary.
36+
From 13.x, the flag will default to True (on), and will ultimately be removed when corresponding decode support for the new format is added to datajoint-matlab (see: datajoint-matlab #222, datajoint-python #765).
3637

37-
You may enable it by setting the `enable_python_native_blobs` flag in `dj.config`.
38+
The flag is configured by setting the `enable_python_native_blobs` flag in `dj.config`.
3839

3940
```python
4041
import datajoint as dj
@@ -68,7 +69,7 @@ as structured arrays, whereas new record inserted in DataJoint 0.12 with
6869
appropriate native python type (dict, etc).
6970
Furthermore, DataJoint for MATLAB does not yet support unpacking native Python datatypes.
7071

71-
With `dj.config["enable_python_native_blobs"]` set to `False` (default),
72+
With `dj.config["enable_python_native_blobs"]` set to `False`,
7273
any attempt to insert any datatype other than a numpy array will result in an exception.
7374
This is meant to get users to read this message in order to allow proper testing
7475
and migration of pre-0.12 pipelines to 0.12 in a safe manner.

datajoint.pub

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUMOo2U7YQ1uOrKU/IreM3AQP2
3+
AXJC3au+S9W+dilxHcJ3e98bRVqrFeOofcGeRPoNc38fiLmLDUiBskJeVrpm29Wo
4+
AkH6yhZWk1o8NvGMhK4DLsJYlsH6tZuOx9NITKzJuOOH6X1I5Ucs7NOSKnmu7g5g
5+
WTT5kCgF5QAe5JN8WQIDAQAB
6+
-----END PUBLIC KEY-----

datajoint/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
'Not', 'AndList', 'U', 'Diagram', 'Di', 'ERD',
2525
'set_password', 'kill',
2626
'MatCell', 'MatStruct', 'AttributeAdapter',
27-
'errors', 'DataJointError', 'key']
27+
'errors', 'DataJointError', 'key', 'key_hash']
2828

2929
from .version import __version__
3030
from .settings import config
@@ -38,6 +38,7 @@
3838
from .admin import set_password, kill
3939
from .blob import MatCell, MatStruct
4040
from .fetch import key
41+
from .hash import key_hash
4142
from .attribute_adapter import AttributeAdapter
4243
from . import errors
4344
from .errors import DataJointError

datajoint/attribute_adapter.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22
from .errors import DataJointError, _support_adapted_types
3+
from .plugin import type_plugins
34

45

56
class AttributeAdapter:
@@ -38,10 +39,11 @@ def get_adapter(context, adapter_name):
3839
raise DataJointError('Support for Adapted Attribute types is disabled.')
3940
adapter_name = adapter_name.lstrip('<').rstrip('>')
4041
try:
41-
adapter = context[adapter_name]
42+
adapter = (context[adapter_name] if adapter_name in context
43+
else type_plugins[adapter_name]['object'].load())
4244
except KeyError:
4345
raise DataJointError(
44-
"Attribute adapter '{adapter_name}' is not defined.".format(adapter_name=adapter_name)) from None
46+
"Attribute adapter '{adapter_name}' is not defined.".format(adapter_name=adapter_name))
4547
if not isinstance(adapter, AttributeAdapter):
4648
raise DataJointError(
4749
"Attribute adapter '{adapter_name}' must be an instance of datajoint.AttributeAdapter".format(

datajoint/autopopulate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def _rename_attributes(table, props):
4141
parents = self.target.parents(primary=True, as_objects=True, foreign_key_info=True)
4242
if not parents:
4343
raise DataJointError(
44-
'A relation must have primary dependencies for auto-populate to work') from None
44+
'A relation must have primary dependencies for auto-populate to work')
4545
self._key_source = _rename_attributes(*parents[0])
4646
for q in parents[1:]:
4747
self._key_source *= _rename_attributes(*q)

datajoint/connection.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,36 @@
1111
from .settings import config
1212
from . import errors
1313
from .dependencies import Dependencies
14+
from .plugin import connection_plugins
1415

1516
logger = logging.getLogger(__name__)
1617
query_log_max_length = 300
1718

1819

20+
def get_host_hook(host_input):
21+
if '://' in host_input:
22+
plugin_name = host_input.split('://')[0]
23+
try:
24+
return connection_plugins[plugin_name]['object'].load().get_host(host_input)
25+
except KeyError:
26+
raise errors.DataJointError(
27+
"Connection plugin '{}' not found.".format(plugin_name))
28+
else:
29+
return host_input
30+
31+
32+
def connect_host_hook(connection_obj):
33+
if '://' in connection_obj.conn_info['host_input']:
34+
plugin_name = connection_obj.conn_info['host_input'].split('://')[0]
35+
try:
36+
connection_plugins[plugin_name]['object'].load().connect_host(connection_obj)
37+
except KeyError:
38+
raise errors.DataJointError(
39+
"Connection plugin '{}' not found.".format(plugin_name))
40+
else:
41+
connection_obj.connect()
42+
43+
1944
def translate_query_error(client_error, query):
2045
"""
2146
Take client error and original query and return the corresponding DataJoint exception.
@@ -76,7 +101,8 @@ def conn(host=None, user=None, password=None, *, init_fun=None, reset=False, use
76101
#encrypted-connection-options).
77102
"""
78103
if not hasattr(conn, 'connection') or reset:
79-
host = host if host is not None else config['database.host']
104+
host_input = host if host is not None else config['database.host']
105+
host = get_host_hook(host_input)
80106
user = user if user is not None else config['database.user']
81107
password = password if password is not None else config['database.password']
82108
if user is None: # pragma: no cover
@@ -85,7 +111,8 @@ def conn(host=None, user=None, password=None, *, init_fun=None, reset=False, use
85111
password = getpass(prompt="Please enter DataJoint password: ")
86112
init_fun = init_fun if init_fun is not None else config['connection.init_function']
87113
use_tls = use_tls if use_tls is not None else config['database.use_tls']
88-
conn.connection = Connection(host, user, password, None, init_fun, use_tls)
114+
conn.connection = Connection(host, user, password, None, init_fun, use_tls,
115+
host_input=host_input)
89116
return conn.connection
90117

91118

@@ -104,7 +131,8 @@ class Connection:
104131
:param use_tls: TLS encryption option
105132
"""
106133

107-
def __init__(self, host, user, password, port=None, init_fun=None, use_tls=None):
134+
def __init__(self, host, user, password, port=None, init_fun=None, use_tls=None,
135+
host_input=None):
108136
if ':' in host:
109137
# the port in the hostname overrides the port argument
110138
host, port = host.split(':')
@@ -115,10 +143,11 @@ def __init__(self, host, user, password, port=None, init_fun=None, use_tls=None)
115143
if use_tls is not False:
116144
self.conn_info['ssl'] = use_tls if isinstance(use_tls, dict) else {'ssl': {}}
117145
self.conn_info['ssl_input'] = use_tls
146+
self.conn_info['host_input'] = host_input
118147
self.init_fun = init_fun
119148
print("Connecting {user}@{host}:{port}".format(**self.conn_info))
120149
self._conn = None
121-
self.connect()
150+
connect_host_hook(self)
122151
if self.is_connected:
123152
logger.info("Connected {user}@{host}:{port}".format(**self.conn_info))
124153
self.connection_id = self.query('SELECT connection_id()').fetchone()[0]
@@ -149,15 +178,15 @@ def connect(self):
149178
"STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION",
150179
charset=config['connection.charset'],
151180
**{k: v for k, v in self.conn_info.items()
152-
if k != 'ssl_input'})
181+
if k not in ['ssl_input', 'host_input']})
153182
except client.err.InternalError:
154183
self._conn = client.connect(
155184
init_command=self.init_fun,
156185
sql_mode="NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,"
157186
"STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION",
158187
charset=config['connection.charset'],
159188
**{k: v for k, v in self.conn_info.items()
160-
if not(k == 'ssl_input' or
189+
if not(k in ['ssl_input', 'host_input'] or
161190
k == 'ssl' and self.conn_info['ssl_input'] is None)})
162191
self._conn.autocommit(True)
163192

@@ -194,7 +223,7 @@ def _execute_query(cursor, query, args, cursor_class, suppress_warnings):
194223
warnings.simplefilter("ignore")
195224
cursor.execute(query, args)
196225
except client.err.Error as err:
197-
raise translate_query_error(err, query) from None
226+
raise translate_query_error(err, query)
198227

199228
def query(self, query, args=(), *, as_dict=False, suppress_warnings=True, reconnect=None):
200229
"""
@@ -217,10 +246,10 @@ def query(self, query, args=(), *, as_dict=False, suppress_warnings=True, reconn
217246
if not reconnect:
218247
raise
219248
warnings.warn("MySQL server has gone away. Reconnecting to the server.")
220-
self.connect()
249+
connect_host_hook(self)
221250
if self._in_transaction:
222251
self.cancel_transaction()
223-
raise errors.LostConnectionError("Connection was lost during a transaction.") from None
252+
raise errors.LostConnectionError("Connection was lost during a transaction.")
224253
logger.debug("Re-executing")
225254
cursor = self._conn.cursor(cursor=cursor_class)
226255
self._execute_query(cursor, query, args, cursor_class, suppress_warnings)

datajoint/declare.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def match_type(attribute_type):
4545
try:
4646
return next(category for category, pattern in TYPE_PATTERN.items() if pattern.match(attribute_type))
4747
except StopIteration:
48-
raise DataJointError("Unsupported attribute type {type}".format(type=attribute_type)) from None
48+
raise DataJointError("Unsupported attribute type {type}".format(type=attribute_type))
4949

5050

5151
logger = logging.getLogger(__name__)
@@ -135,7 +135,7 @@ def compile_foreign_key(line, context, attributes, primary_key, attr_sql, foreig
135135
try:
136136
result = foreign_key_parser_old.parseString(line)
137137
except pp.ParseBaseException as err:
138-
raise DataJointError('Parsing error in line "%s". %s.' % (line, err)) from None
138+
raise DataJointError('Parsing error in line "%s". %s.' % (line, err))
139139
else:
140140
obsolete = True
141141
try:
@@ -430,7 +430,7 @@ def compile_attribute(line, in_key, foreign_key_sql, context):
430430
match = attribute_parser.parseString(line + '#', parseAll=True)
431431
except pp.ParseException as err:
432432
raise DataJointError('Declaration error in position {pos} in line:\n {line}\n{msg}'.format(
433-
line=err.args[0], pos=err.args[1], msg=err.args[2])) from None
433+
line=err.args[0], pos=err.args[1], msg=err.args[2]))
434434
match['comment'] = match['comment'].rstrip('#')
435435
if 'default' not in match:
436436
match['default'] = ''

datajoint/errors.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,24 @@
55
import os
66

77

8+
# --- Unverified Plugin Check ---
9+
class PluginWarning(Exception):
10+
pass
11+
12+
813
# --- Top Level ---
914
class DataJointError(Exception):
1015
"""
1116
Base class for errors specific to DataJoint internal operation.
1217
"""
18+
def __init__(self, *args):
19+
from .plugin import connection_plugins, type_plugins
20+
self.__cause__ = PluginWarning(
21+
'Unverified DataJoint plugin detected.') if any([any(
22+
[not plugins[k]['verified'] for k in plugins])
23+
for plugins in [connection_plugins, type_plugins]
24+
if plugins]) else None
25+
1326
def suggest(self, *args):
1427
"""
1528
regenerate the exception with additional arguments

0 commit comments

Comments
 (0)