Skip to content

Commit 3b40324

Browse files
authored
Merge pull request #8 from scitran/cgc/host-support
Add support for hosts.
2 parents 58407ce + 0c25f7f commit 3b40324

6 files changed

Lines changed: 94 additions & 39 deletions

File tree

examples/flywheel_analyzer_afq.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def afq_inputs(analyses, **kwargs):
1919
)
2020

2121
if __name__ == '__main__':
22-
fa.run([
23-
fa.define_analysis('dtiinit', dtiinit_inputs),
24-
fa.define_analysis('afq', afq_inputs),
25-
], project=fa.find_project(label='ENGAGE'))
22+
with fa.installed_client():
23+
fa.run([
24+
fa.define_analysis('dtiinit', dtiinit_inputs),
25+
fa.define_analysis('afq', afq_inputs),
26+
], project=fa.find_project(label='ENGAGE'))

examples/flywheel_analyzer_engage.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import scitran_client.flywheel_analyzer as fa
2+
from scitran_client import ScitranClient
23

34

45
# XXX at least make this be just the first thing without ' 2'?
@@ -17,7 +18,7 @@
1718

1819
def _find_file(container, glob):
1920
return (
20-
container.find_file(glob) or
21+
container.find_file(glob, default=None) or
2122
# HACK because flywheel does not currently support nested files
2223
# in output folders, we are flattening hierarchy by replacing
2324
# forward slashes with @@
@@ -80,16 +81,17 @@ def first_level_model_inputs(acquisition_label, analyses, acquisitions):
8081
), dict(task_type=label_to_task_type[acquisition_label])
8182

8283
if __name__ == '__main__':
83-
fa.run([
84-
define_analysis('reactivity-preprocessing', 'go-no-go 2', reactivity_inputs),
85-
define_analysis('connectivity-preprocessing', 'go-no-go 2', connectivity_inputs),
86-
define_analysis('first-level-models', 'go-no-go 2', first_level_model_inputs),
87-
88-
define_analysis('reactivity-preprocessing', 'conscious 2', reactivity_inputs),
89-
define_analysis('connectivity-preprocessing', 'conscious 2', connectivity_inputs),
90-
define_analysis('first-level-models', 'conscious 2', first_level_model_inputs),
91-
92-
define_analysis('reactivity-preprocessing', 'nonconscious 2', reactivity_inputs),
93-
define_analysis('connectivity-preprocessing', 'nonconscious 2', connectivity_inputs),
94-
define_analysis('first-level-models', 'nonconscious 2', first_level_model_inputs),
95-
], project=fa.find_project(label='ENGAGE'), session_limit=1)
84+
with fa.installed_client(ScitranClient('https://flywheel-cni.scitran.stanford.edu')):
85+
fa.run([
86+
define_analysis('reactivity-preprocessing', 'go-no-go 2', reactivity_inputs),
87+
define_analysis('connectivity-preprocessing', 'go-no-go 2', connectivity_inputs),
88+
define_analysis('first-level-models', 'go-no-go 2', first_level_model_inputs),
89+
90+
define_analysis('reactivity-preprocessing', 'conscious 2', reactivity_inputs),
91+
define_analysis('connectivity-preprocessing', 'conscious 2', connectivity_inputs),
92+
define_analysis('first-level-models', 'conscious 2', first_level_model_inputs),
93+
94+
define_analysis('reactivity-preprocessing', 'nonconscious 2', reactivity_inputs),
95+
define_analysis('connectivity-preprocessing', 'nonconscious 2', connectivity_inputs),
96+
define_analysis('first-level-models', 'nonconscious 2', first_level_model_inputs),
97+
], project=fa.find_project(label='ENGAGE'), session_limit=1)
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
from scitran_client import ScitranClient
12
import scitran_client.flywheel_analyzer as fa
23

3-
D99 = fa.find(
4-
fa.request('sessions/588bd1ac449f9800159305c2/acquisitions'),
5-
label='atlas')
4+
client = ScitranClient('https://flywheel.scitran.stanford.edu')
5+
6+
with fa.installed_client(client):
7+
D99 = fa.find(
8+
client.request('sessions/588bd1ac449f9800159305c2/acquisitions').json(),
9+
label='atlas')
610

711

812
def anatomical_warp_inputs(acquisitions, **kwargs):
@@ -14,6 +18,7 @@ def anatomical_warp_inputs(acquisitions, **kwargs):
1418
)
1519

1620
if __name__ == '__main__':
17-
fa.run([
18-
fa.define_analysis('afni-brain-warp', anatomical_warp_inputs, label='anatomical warp'),
19-
], project=fa.find_project(label='showdes'), max_workers=2)
21+
with fa.installed_client(client):
22+
fa.run([
23+
fa.define_analysis('afni-brain-warp', anatomical_warp_inputs, label='anatomical warp'),
24+
], project=fa.find_project(label='showdes'), max_workers=2)

scitran_client/flywheel_analyzer.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from fnmatch import fnmatch
88
from collections import namedtuple, Counter
99
import math
10+
from contextlib import contextmanager
1011

1112

1213
def _sleep(seconds):
@@ -41,26 +42,46 @@ def define_analysis(gear_name, create_inputs, label=None):
4142

4243

4344
class FlywheelFileContainer(dict):
44-
def find_file(self, pattern):
45+
def find_file(self, pattern, **kwargs):
4546
'''Find a file in this container with a name that matches pattern.
4647
4748
This will look for a file in this container that matches the supplied
4849
pattern. Matching uses the fnmatch python library, which does Unix
4950
filename pattern matching.
5051
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+
5156
To find a specific file, you can simply match by name:
5257
> acquisition.find_file('anatomical.nii.gz')
5358
5459
To find a file with an extension, you can use a Unix-style pattern:
5560
> 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)
5664
'''
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
5967

6068
# XXX if is_analysis then we should require the file to be an output??
61-
f = next(
69+
matches = [
6270
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]
6485

6586
return dict(
6687
type='analysis' if is_analysis else 'acquisition',
@@ -104,8 +125,7 @@ class ShuttingDownException(Exception):
104125

105126
def request(*args, **kwargs):
106127
# 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`.'
109129
response = state['client']._request(*args, **kwargs)
110130
return json.loads(response.text)
111131

@@ -223,6 +243,24 @@ def done(f):
223243
raise
224244

225245

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+
226264
def run(operations, project=None, max_workers=10, session_limit=None):
227265
"""Run a sequence of FlywheelAnalysisOperations.
228266

scitran_client/st_auth.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,19 @@ def _prompt_for_valid_api_key(url):
2525
return api_key
2626

2727

28-
def create_token(instance_name, config_dir):
28+
def create_token(instance_name_or_host, config_dir):
2929
'''
3030
Get an API key for this instance, requesting a new one if no previous one exists.
3131
3232
Args:
33-
instance_name (str): The instance to generate a token for.
33+
instance_name (str): The instance to generate a token for. Should only have one of `instance_name` or `host`.
34+
host (str): The host we are are trying to generate a token for.
3435
config_dir (str): Path of directory where the tokens live.
3536
3637
Returns:
3738
Python tuple: (token (str), client_url (str)): (The requested token, the base url for this client)
3839
'''
40+
3941
if not os.path.exists(config_dir):
4042
os.mkdir(config_dir)
4143

@@ -47,13 +49,21 @@ def create_token(instance_name, config_dir):
4749
with open(auth_path, 'r') as f:
4850
auth_config = json.load(f)
4951

50-
auth = auth_config.get(instance_name)
52+
matches = [
53+
a
54+
for name, a in auth_config.iteritems()
55+
if a['url'] == instance_name_or_host or name == instance_name_or_host
56+
]
57+
assert len(matches) <= 1, \
58+
'Too many matches for for {}. found: {}'.format(instance_name_or_host, matches)
59+
60+
auth = matches and matches[0]
5161

5262
example = json.dumps(dict(api_key='<secret>', url='https://myflywheel.io'), indent=4)
5363
assert isinstance(auth, dict) and set(auth.keys()) == {'api_key', 'url'}, (
5464
'Missing or invalid entry in {0} for instance {1}. You can fix this issue by '
5565
'adding an entry for {1} or making it look more like this: {2}'
56-
.format(auth_path, instance_name, example))
66+
.format(auth_path, instance_name_or_host, example))
5767

5868
# We just wipe out keys that are invalid.
5969
if auth['api_key'] and not _is_valid_token(auth['url'], auth['api_key']):

scitran_client/st_client.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ class ScitranClient(object):
5555
'''Handles api calls to a certain instance.
5656
5757
Attributes:
58-
instance (str): instance name.
59-
base_url (str): The base url of that instance, as returned by stAuth.create_token(instance, st_dir)
58+
instance_name (str): instance name or host.
6059
token (str): Authentication token.
6160
st_dir (str): The path to the directory where token and authentication file are kept for this instance.
6261
'''
@@ -70,7 +69,7 @@ def __init__(self,
7069
gear_out_dir=DEFAULT_OUTPUT_DIR):
7170

7271
self.session = requests.Session()
73-
self.instance = instance_name
72+
self.instance_name_or_host = instance_name
7473
self.st_dir = st_dir
7574
self._authenticate()
7675
self.debug = debug
@@ -139,7 +138,7 @@ def _authenticate_request(self, request):
139138
return request
140139

141140
def _authenticate(self):
142-
self.token, self.base_url = st_auth.create_token(self.instance, self.st_dir)
141+
self.token, self.base_url = st_auth.create_token(self.instance_name_or_host, self.st_dir)
143142
self.base_url = urlparse.urljoin(self.base_url, 'api/')
144143

145144
def _request(self, *args, **kwargs):

0 commit comments

Comments
 (0)