-
Notifications
You must be signed in to change notification settings - Fork 90
Attachments and configurable blobs #532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 50 commits
5918b2c
bc85f21
92553b0
c127693
1b188da
13573c5
f3dd5b3
046291a
1f5fe9d
3350516
acc07fb
c11bbbf
390b0b7
2c04d74
43e9c76
51336ff
cee588e
c04f974
9de4782
0c35af1
1588303
f265674
bcebce5
50f17ce
f141aa7
6310c7d
7cb1d3f
aa72832
4818bbb
3aa936e
bdf8195
173bf1d
61c2ce7
aa6a2ce
f49cf22
eea3e20
7ee6134
6701abb
346f47f
b2087aa
332cfd6
0c491e2
7e51e4f
0434fc8
484e926
1a83fe6
4c8b6eb
bf66d64
8f4e8f9
0826d94
fb14029
90cf697
afeadb1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| """ | ||
| functionality for attaching files | ||
| """ | ||
| from os import path | ||
| from itertools import count, chain | ||
|
|
||
|
|
||
| def load(local_path): | ||
| """ make an attachment from a local file """ | ||
| with open(local_path, mode='rb') as f: # b is important -> binary | ||
| contents = f.read() | ||
| return str.encode(path.basename(local_path)) + b'\0' + contents | ||
|
|
||
|
|
||
| def save(buffer, save_path='.'): | ||
| """ save attachment from memory buffer into the save_path """ | ||
| p = buffer.find(b'\0') | ||
| file_path = path.abspath(path.join(save_path, buffer[:p].decode())) | ||
|
|
||
| if path.isfile(file_path): | ||
| # generate a new filename | ||
| file, ext = path.splitext(file_path) | ||
| file_path = next(f for f in ('%s_%04x%s' % (file, n, ext) for n in count()) | ||
| if not path.isfile(f)) | ||
|
|
||
| with open(file_path, mode='wb') as f: | ||
| f.write(buffer[p+1:]) | ||
| return file_path |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -197,7 +197,6 @@ def declare(full_table_name, definition, context): | |
| :param definition: DataJoint table definition | ||
| :param context: dictionary of objects that might be referred to in the table. | ||
| """ | ||
|
|
||
| table_name = full_table_name.strip('`').split('.')[1] | ||
| if len(table_name) > MAX_TABLE_NAME_LENGTH: | ||
| raise DataJointError( | ||
|
|
@@ -271,12 +270,13 @@ def compile_attribute(line, in_key, foreign_key_sql): | |
| match['default'] = '' | ||
| match = {k: v.strip() for k, v in match.items()} | ||
| match['nullable'] = match['default'].lower() == 'null' | ||
| accepted_datatype = r'time|date|year|enum|(var)?char|float|real|double|decimal|numeric|' \ | ||
| r'(tiny|small|medium|big)?int|bool|' \ | ||
| r'(tiny|small|medium|long)?blob|external|attach' | ||
| blob_datatype = r'(tiny|small|medium|long)?blob' | ||
| accepted_datatype = ( | ||
| r'time|date|year|enum|(var)?char|float|real|double|decimal|numeric|' | ||
| r'(tiny|small|medium|big)?int|bool|external|attach|' + blob_datatype) | ||
| if re.match(accepted_datatype, match['type'], re.I) is None: | ||
| raise DataJointError('DataJoint does not support datatype "{type}"'.format(**match)) | ||
|
|
||
| is_blob = bool(re.match(blob_datatype, match['type'], re.I)) | ||
| literals = ['CURRENT_TIMESTAMP'] # not to be enclosed in quotes | ||
| if match['nullable']: | ||
| if in_key: | ||
|
|
@@ -291,32 +291,41 @@ def compile_attribute(line, in_key, foreign_key_sql): | |
| match['default'] = 'NOT NULL' | ||
| match['comment'] = match['comment'].replace('"', '\\"') # escape double quotes in comment | ||
|
|
||
| is_external = match['type'].startswith('external') | ||
| is_attachment = match['type'].startswith('attachment') | ||
| if not is_external: | ||
| sql = ('`{name}` {type} {default}' + (' COMMENT "{comment}"' if match['comment'] else '')).format(**match) | ||
| else: | ||
| # process externally stored attribute | ||
| is_configurable = match['type'].startswith(('external', 'blob-', 'attach')) | ||
| is_external = False | ||
| if is_configurable: | ||
| if in_key: | ||
| raise DataJointError('External attributes cannot be primary in:\n%s' % line) | ||
| raise DataJointError('Configurable attributes cannot be primary in:\n%s' % line) | ||
| match['comment'] = ':{type}:{comment}'.format(**match) # insert configurable type into comment | ||
| store_name = match['type'].split('-') | ||
| if store_name[0] != 'external': | ||
| raise DataJointError('External store types must be specified as "external" or "external-<name>"') | ||
| if store_name[0] not in ('external', 'blob', 'attach'): | ||
| raise DataJointError('Invalid attribute type in:\n%s' % line) | ||
|
||
| store_name = '-'.join(store_name[1:]) | ||
| if store_name != '' and not store_name.isidentifier(): | ||
| if store_name and not store_name.isidentifier(): | ||
| raise DataJointError( | ||
| 'The external store name `{type}` is invalid. Make like a python identifier.'.format(**match)) | ||
| if len(store_name) > STORE_NAME_LENGTH: | ||
| raise DataJointError( | ||
| 'The external store name `{type}` is too long. Must be <={max_len} characters.'.format( | ||
| max_len=STORE_NAME_LENGTH, **match)) | ||
| if not match['default'] in ('DEFAULT NULL', 'NOT NULL'): | ||
| raise DataJointError('The only acceptable default value for an external field is null in:\n%s' % line) | ||
| if match['type'] not in config: | ||
| raise DataJointError('The external store `{type}` is not configured.'.format(**match)) | ||
| spec = config.get_store_spec(store_name) | ||
| is_external = spec['protocol'] in {'s3', 'file'} | ||
| if not is_external: | ||
| is_blob = re.match(blob_datatype, spec['protocol'], re.I) | ||
| if not is_blob: | ||
| raise DataJointError('Invalid protocol {protocol} in external store in:\n{line}'.format( | ||
| line=line, **spec)) | ||
| match['type'] = spec['protocol'] | ||
|
|
||
| if (is_external or is_blob) and match['default'] not in ('DEFAULT NULL', 'NOT NULL'): | ||
| raise DataJointError( | ||
| 'The default value for a blob or attachment can only be NULL in:\n%s' % line) | ||
|
|
||
| # append external configuration name to the end of the comment | ||
| sql = '`{name}` {hash_type} {default} COMMENT ":{type}:{comment}"'.format( | ||
| if not is_external: | ||
| sql = ('`{name}` {type} {default}' + (' COMMENT "{comment}"' if match['comment'] else '')).format(**match) | ||
| else: | ||
| # add hash field with a dependency on the ~external table | ||
| sql = '`{name}` {hash_type} {default} COMMENT "{comment}"'.format( | ||
| hash_type=HASH_DATA_TYPE, **match) | ||
| foreign_key_sql.append( | ||
| "FOREIGN KEY (`{name}`) REFERENCES {{external_table}} (`hash`) " | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,8 +50,8 @@ class ERD: | |
| """ | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| warnings.warn('ERD functionality depends on matplotlib and pygraphviz. Please install both of these ' | ||
| 'libraries to enable the ERD feature.') | ||
| warnings.warn('ERD functionality depends on matplotlib, networkx, and pygraphviz. ' | ||
| 'Please install both of these libraries to enable the ERD feature.') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are now three libraries mentioned. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
| else: | ||
| class ERD(nx.DiGraph): | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean 2019?