Skip to content

Commit c871023

Browse files
Merge pull request #730 from guzman-raphael/plugin
Plugin Infrastructure
2 parents 31edaea + 815bda8 commit c871023

22 files changed

+236
-76
lines changed

LNX-docker-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ services:
3737
pip freeze | grep datajoint;
3838
coveralls;
3939
nosetests -vsw tests --with-coverage --cover-package=datajoint;
40-
# jupyter notebook;
4140
"
4241
# ports:
4342
# - "8888:8888"

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/attribute_adapter.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import re
2+
import os
23
from .errors import DataJointError, _support_adapted_types
4+
from .plugin import override
35

46

57
class AttributeAdapter:
@@ -30,6 +32,9 @@ def put(self, obj):
3032
raise NotImplementedError('Undefined attribute adapter')
3133

3234

35+
override('attribute_adapter', globals())
36+
37+
3338
def get_adapter(context, adapter_name):
3439
"""
3540
Extract the AttributeAdapter object by its name from the context and validate.
@@ -38,10 +43,12 @@ def get_adapter(context, adapter_name):
3843
raise DataJointError('Support for Adapted Attribute types is disabled.')
3944
adapter_name = adapter_name.lstrip('<').rstrip('>')
4045
try:
41-
adapter = context[adapter_name]
46+
source_module_name, adapter_name = os.path.splitext(adapter_name)
47+
adapter = context[source_module_name] if adapter_name == '' else getattr(
48+
__import__(source_module_name, fromlist=[adapter_name[1:]]), adapter_name[1:])
4249
except KeyError:
4350
raise DataJointError(
44-
"Attribute adapter '{adapter_name}' is not defined.".format(adapter_name=adapter_name)) from None
51+
"Attribute adapter '{adapter_name}' is not defined.".format(adapter_name=adapter_name))
4552
if not isinstance(adapter, AttributeAdapter):
4653
raise DataJointError(
4754
"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
@@ -48,7 +48,7 @@ def parent_gen(self):
4848
try:
4949
self._key_source = next(parents)
5050
except StopIteration:
51-
raise DataJointError('A relation must have primary dependencies for auto-populate to work') from None
51+
raise DataJointError('A table must have primary dependencies for auto-populate to work')
5252
for q in parents:
5353
self._key_source *= q
5454
return self._key_source

datajoint/connection.py

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

1516
# client errors to catch
1617
client_errors = (client.err.InterfaceError, client.err.DatabaseError)
1718

1819

20+
def get_host_hook(host_input):
21+
return host_input
22+
23+
24+
def connect_host_hook(connection_obj):
25+
connection_obj.connect()
26+
27+
28+
override('connection', globals(), ['get_host_hook', 'connect_host_hook'])
29+
30+
1931
def translate_query_error(client_error, query):
2032
"""
2133
Take client error and original query and return the corresponding DataJoint exception.
@@ -76,7 +88,8 @@ def conn(host=None, user=None, password=None, *, init_fun=None, reset=False, use
7688
#encrypted-connection-options).
7789
"""
7890
if not hasattr(conn, 'connection') or reset:
79-
host = host if host is not None else config['database.host']
91+
host_input = host if host is not None else config['database.host']
92+
host = get_host_hook(host_input)
8093
user = user if user is not None else config['database.user']
8194
password = password if password is not None else config['database.password']
8295
if user is None: # pragma: no cover
@@ -85,7 +98,8 @@ def conn(host=None, user=None, password=None, *, init_fun=None, reset=False, use
8598
password = getpass(prompt="Please enter DataJoint password: ")
8699
init_fun = init_fun if init_fun is not None else config['connection.init_function']
87100
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)
101+
conn.connection = Connection(host, user, password, None, init_fun, use_tls,
102+
host_input=host_input)
89103
return conn.connection
90104

91105

@@ -104,7 +118,8 @@ class Connection:
104118
:param use_tls: TLS encryption option
105119
"""
106120

107-
def __init__(self, host, user, password, port=None, init_fun=None, use_tls=None):
121+
def __init__(self, host, user, password, port=None, init_fun=None, use_tls=None,
122+
host_input=None):
108123
if ':' in host:
109124
# the port in the hostname overrides the port argument
110125
host, port = host.split(':')
@@ -115,10 +130,11 @@ def __init__(self, host, user, password, port=None, init_fun=None, use_tls=None)
115130
if use_tls is not False:
116131
self.conn_info['ssl'] = use_tls if isinstance(use_tls, dict) else {'ssl': {}}
117132
self.conn_info['ssl_input'] = use_tls
133+
self.conn_info['host_input'] = host_input
118134
self.init_fun = init_fun
119135
print("Connecting {user}@{host}:{port}".format(**self.conn_info))
120136
self._conn = None
121-
self.connect()
137+
connect_host_hook(self)
122138
if self.is_connected:
123139
logger.info("Connected {user}@{host}:{port}".format(**self.conn_info))
124140
self.connection_id = self.query('SELECT connection_id()').fetchone()[0]
@@ -149,15 +165,15 @@ def connect(self):
149165
"STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION",
150166
charset=config['connection.charset'],
151167
**{k: v for k, v in self.conn_info.items()
152-
if k != 'ssl_input'})
168+
if k not in ['ssl_input', 'host_input']})
153169
except client.err.InternalError:
154170
self._conn = client.connect(
155171
init_command=self.init_fun,
156172
sql_mode="NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,"
157173
"STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION",
158174
charset=config['connection.charset'],
159175
**{k: v for k, v in self.conn_info.items()
160-
if not(k == 'ssl_input' or
176+
if not(k in ['ssl_input', 'host_input'] or
161177
k == 'ssl' and self.conn_info['ssl_input'] is None)})
162178
self._conn.autocommit(True)
163179

@@ -193,7 +209,7 @@ def __execute_query(cursor, query, args, cursor_class, suppress_warnings):
193209
warnings.simplefilter("ignore")
194210
cursor.execute(query, args)
195211
except client_errors as err:
196-
raise translate_query_error(err, query) from None
212+
raise translate_query_error(err, query)
197213

198214
def query(self, query, args=(), *, as_dict=False, suppress_warnings=True, reconnect=None):
199215
"""
@@ -216,10 +232,10 @@ def query(self, query, args=(), *, as_dict=False, suppress_warnings=True, reconn
216232
if not reconnect:
217233
raise
218234
warnings.warn("MySQL server has gone away. Reconnecting to the server.")
219-
self.connect()
235+
connect_host_hook(self)
220236
if self._in_transaction:
221237
self.cancel_transaction()
222-
raise errors.LostConnectionError("Connection was lost during a transaction.") from None
238+
raise errors.LostConnectionError("Connection was lost during a transaction.")
223239
logger.debug("Re-executing")
224240
cursor = self._conn.cursor(cursor=cursor_class)
225241
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,22 @@
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 discovered_plugins
20+
self.__cause__ = PluginWarning(
21+
'Unverified DataJoint plugin detected.') if discovered_plugins and any(
22+
[not discovered_plugins[k]['verified'] for k in discovered_plugins]) else None
23+
1324
def suggest(self, *args):
1425
"""
1526
regenerate the exception with additional arguments

datajoint/expression.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def prep_value(k, v):
129129
try:
130130
v = uuid.UUID(v)
131131
except (AttributeError, ValueError):
132-
raise DataJointError('Badly formed UUID {v} in restriction by `{k}`'.format(k=k, v=v)) from None
132+
raise DataJointError('Badly formed UUID {v} in restriction by `{k}`'.format(k=k, v=v))
133133
return "X'%s'" % binascii.hexlify(v.bytes).decode()
134134
if isinstance(v, (datetime.date, datetime.datetime, datetime.time, decimal.Decimal)):
135135
return '"%s"' % v

datajoint/external.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def upload_filepath(self, local_filepath):
227227
relative_filepath = str(local_filepath.relative_to(self.spec['stage']).as_posix())
228228
except ValueError:
229229
raise DataJointError('The path {path} is not in stage {stage}'.format(
230-
path=local_filepath.parent, **self.spec)) from None
230+
path=local_filepath.parent, **self.spec))
231231
uuid = uuid_from_buffer(init_string=relative_filepath) # hash relative path, not contents
232232
contents_hash = uuid_from_file(local_filepath)
233233

datajoint/heading.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ def init_from_database(self, conn, database, table_name, context):
250250
url = "https://docs.datajoint.io/python/admin/5-blob-config.html" \
251251
"#migration-between-datajoint-v0-11-and-v0-12"
252252
raise DataJointError('Legacy datatype `{type}`. Migrate your external stores to '
253-
'datajoint 0.12: {url}'.format(url=url, **attr)) from None
254-
raise DataJointError('Unknown attribute type `{type}`'.format(**attr)) from None
253+
'datajoint 0.12: {url}'.format(url=url, **attr))
254+
raise DataJointError('Unknown attribute type `{type}`'.format(**attr))
255255
if category == 'FILEPATH' and not _support_filepath_types():
256256
raise DataJointError("""
257257
The filepath data type is disabled until complete validation.

0 commit comments

Comments
 (0)