|
7 | 7 | from fnmatch import fnmatch |
8 | 8 | from collections import namedtuple, Counter |
9 | 9 | import math |
| 10 | +from contextlib import contextmanager |
10 | 11 |
|
11 | 12 |
|
12 | 13 | def _sleep(seconds): |
@@ -41,26 +42,46 @@ def define_analysis(gear_name, create_inputs, label=None): |
41 | 42 |
|
42 | 43 |
|
43 | 44 | class FlywheelFileContainer(dict): |
44 | | - def find_file(self, pattern): |
| 45 | + def find_file(self, pattern, **kwargs): |
45 | 46 | '''Find a file in this container with a name that matches pattern. |
46 | 47 |
|
47 | 48 | This will look for a file in this container that matches the supplied |
48 | 49 | pattern. Matching uses the fnmatch python library, which does Unix |
49 | 50 | filename pattern matching. |
50 | 51 |
|
| 52 | + kwargs['default'] - like `next`, when a default is supplied, it will be |
| 53 | + returned when there are no matches. When a default is not supplied, an |
| 54 | + exception will be thrown. |
| 55 | +
|
51 | 56 | To find a specific file, you can simply match by name: |
52 | 57 | > acquisition.find_file('anatomical.nii.gz') |
53 | 58 |
|
54 | 59 | To find a file with an extension, you can use a Unix-style pattern: |
55 | 60 | > stimulus_onsets.find_file('*.txt') |
| 61 | +
|
| 62 | + When looking for a file that might be missing, supply a default value: |
| 63 | + > partial_set_of_files.find_file('*.txt', default=None) |
56 | 64 | ''' |
57 | | - # TODO make sure this throws for missing or multiple files. |
58 | | - is_analysis = 'job' in self and 'state' in self |
| 65 | + has_default = 'default' in kwargs |
| 66 | + is_analysis = 'job' in self |
59 | 67 |
|
60 | 68 | # XXX if is_analysis then we should require the file to be an output?? |
61 | | - f = next( |
| 69 | + matches = [ |
62 | 70 | f for f in self['files'] |
63 | | - if fnmatch(f['name'], pattern)) |
| 71 | + if fnmatch(f['name'], pattern)] |
| 72 | + |
| 73 | + assert len(matches) <= 1, ( |
| 74 | + 'Multiple matches found for pattern "{}" in container {}. Patterns should uniquely identify a file.' |
| 75 | + .format(pattern, self['_id'])) |
| 76 | + if not matches: |
| 77 | + if has_default: |
| 78 | + return kwargs.get('default') |
| 79 | + else: |
| 80 | + raise Exception( |
| 81 | + 'Could not find a match for "{}" in container {}.' |
| 82 | + .format(pattern, self['_id'])) |
| 83 | + |
| 84 | + f = matches[0] |
64 | 85 |
|
65 | 86 | return dict( |
66 | 87 | type='analysis' if is_analysis else 'acquisition', |
@@ -104,8 +125,7 @@ class ShuttingDownException(Exception): |
104 | 125 |
|
105 | 126 | def request(*args, **kwargs): |
106 | 127 | # HACK client is a module variable for now. In the future, we should pass client around. |
107 | | - if 'client' not in state: |
108 | | - state['client'] = ScitranClient() |
| 128 | + assert 'client' in state, 'client must be installed in state before using request. See `installed_client`.' |
109 | 129 | response = state['client']._request(*args, **kwargs) |
110 | 130 | return json.loads(response.text) |
111 | 131 |
|
@@ -223,6 +243,24 @@ def done(f): |
223 | 243 | raise |
224 | 244 |
|
225 | 245 |
|
| 246 | +@contextmanager |
| 247 | +def installed_client(client=None): |
| 248 | + ''' |
| 249 | + This context manager handles the installation of a scitran client for use |
| 250 | + in the flywheel analyzer. Most flywheel analyzer code depends on this being |
| 251 | + set up. |
| 252 | +
|
| 253 | + > with installed_client(): |
| 254 | + > print fa.find_project(label='ADHD study') # actually works! |
| 255 | + ''' |
| 256 | + # BIG HACK |
| 257 | + state['client'] = client or ScitranClient() |
| 258 | + try: |
| 259 | + yield state['client'] |
| 260 | + finally: |
| 261 | + state['client'] = None |
| 262 | + |
| 263 | + |
226 | 264 | def run(operations, project=None, max_workers=10, session_limit=None): |
227 | 265 | """Run a sequence of FlywheelAnalysisOperations. |
228 | 266 |
|
|
0 commit comments