gcis_client.py 17.8 KB
Newer Older
1
2
import urllib
import json
3
from os.path import exists, basename
abuddenberg's avatar
abuddenberg committed
4
import re
5
6
import requests

abuddenberg's avatar
abuddenberg committed
7
from domain import Figure, Image, Dataset, Activity, Person, Organization
8
9
10
11
12
13


def check_image(fn):
    def wrapped(*args, **kwargs):
        # if len(args) < 1 or not isinstance(args[0], Image):
        #     raise Exception('Invalid Image')
14
        if args[1].identifier in (None, ''):
15
            raise Exception('Invalid identifier', args[0].identifier)
16
        return fn(*args, **kwargs)
17
18
19
20

    return wrapped


21
22
23
24
25
26
27
28
29
30
31
32
def exists(fn):
    def wrapped(*args, **kwargs):
        resp = fn(*args, **kwargs)
        if resp.status_code == 200:
            return True
        elif resp.status_code == 404:
            return False
        else:
            raise Exception(resp.text)
    return wrapped


abuddenberg's avatar
abuddenberg committed
33
34
35
36
37
38
39
40
41
42
def http_resp(fn):
    def wrapped(*args, **kwargs):
        resp = fn(*args, **kwargs)
        if resp.status_code == 200:
            return resp
        else:
            raise Exception(resp.text)
    return wrapped


43
44
45
46
47
48
49
50
class AssociationException(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


51
class GcisClient(object):
52
53
    def __init__(self, url, username, password):
        self.base_url = url
54
55
56
        self.s = requests.Session()
        self.s.auth = (username, password)
        self.s.headers.update({'Accept': 'application/json'})
57

58
    @http_resp
59
60
    def create_figure(self, report_id, chapter_id, figure, skip_images=False):
        if figure.identifier in (None, ''):
61
62
63
64
65
            raise Exception('Invalid figure identifier', figure.identifier)

        #Is GCIS not inferring this from the url parameter?
        if figure.chapter_identifier in (None, ''):
            figure.chapter_identifier = chapter_id
66

67
68
        url = '{b}/report/{rpt}/chapter/{chp}/figure/'.format(
            b=self.base_url, rpt=report_id, chp=chapter_id
69
70
        )

71
        resp = self.s.post(url, data=figure.as_json(), verify=False)
72
73

        if skip_images is False:
74
            for image in figure.images:
75
76
                self.create_image(image),
                self.associate_image_with_figure(image.identifier, report_id, figure.identifier)
77

78
        return resp
79

80
    @http_resp
81
    def update_figure(self, report_id, chapter_id, figure, skip_images=False, old_id=None):
82
83
        if figure.identifier in (None, ''):
            raise Exception('Invalid identifier', figure.identifier)
84
85
86
87
88

        #Is GCIS not inferring this from the url parameter?
        if figure.chapter_identifier in (None, ''):
            figure.chapter_identifier = chapter_id

89
90
        url = '{b}/report/{rpt}/chapter/{chp}/figure/{fig}'.format(
            b=self.base_url, rpt=report_id, chp=chapter_id, fig=old_id or figure.identifier
91
92
        )

93
        resp = self.s.post(url, data=figure.as_json(), verify=False)
94

95
        if skip_images is False:
96
            for image in figure.images:
97
98
                self.update_image(image)

99
100
101
        for c in figure.contributors:
            self.associate_contributor_with_figure(c, report_id, chapter_id, figure.identifier)

102
        return resp
103

104
    @http_resp
105
106
    def delete_figure(self, report_id, figure_id):
        url = '{b}/report/{rpt}/figure/{fig}'.format(b=self.base_url, rpt=report_id, fig=figure_id)
107
        return self.s.delete(url, verify=False)
108

109
    @check_image
110
    def create_image(self, image, report_id=None, figure_id=None):
abuddenberg's avatar
abuddenberg committed
111
        url = '{b}/image/'.format(b=self.base_url)
112
        resp = self.s.post(url, data=image.as_json(), verify=False)
113
114
115
116
117
118
        
        if image.local_path is not None:
            self.upload_image_file(image.identifier, image.local_path)
        if figure_id and report_id:
            self.associate_image_with_figure(image.identifier, report_id, figure_id)
        for dataset in image.datasets:
abuddenberg's avatar
abuddenberg committed
119
120
121
122
            if not self.dataset_exists(dataset.identifier):
                self.create_dataset(dataset)
            if not self.activity_exists(dataset.activity.identifier):
                self.create_activity(dataset.activity)
123
124
            self.associate_dataset_with_image(dataset.identifier, image.identifier,
                                              activity_id=dataset.activity.identifier)
abuddenberg's avatar
abuddenberg committed
125
        return resp
126

127
    @check_image
128
129
    def update_image(self, image, old_id=None):
        url = '{b}/image/{img}'.format(b=self.base_url, img=old_id or image.identifier)
130
        for dataset in image.datasets:
abuddenberg's avatar
abuddenberg committed
131
132
133
            self.update_activity(dataset.activity)
            self.associate_dataset_with_image(dataset.identifier, image.identifier,
                                              activity_id=dataset.activity.identifier)
134
135
        for c in image.contributors:
            self.associate_contributor_with_image(c, image.identifier)
136

137
        return self.s.post(url, data=image.as_json(), verify=False)
138

139
    @check_image
140
    @http_resp
141
142
    def delete_image(self, image):
        delete_url = '{b}/image/{img}'.format(b=self.base_url, img=image.identifier)
143
        return self.s.delete(delete_url, verify=False)
144

145
    @http_resp
146
147
    def associate_image_with_figure(self, image_id, report_id, figure_id):
        url = '{b}/report/{rpt}/figure/rel/{fig}'.format(b=self.base_url, rpt=report_id, fig=figure_id)
148
        return self.s.post(url, data=json.dumps({'add_image_identifier': image_id}), verify=False)
149

150
    @http_resp
151
    def upload_image_file(self, image_id, local_path):
152
        url = '{b}/image/files/{id}/{fn}'.format(b=self.base_url, id=image_id, fn=basename(local_path))
153
        # For future multi-part encoding support
154
        # return self.s.put(url, headers=headers, files={'file': (filename, open(filepath, 'rb'))})
155
156
157
        if not exists(local_path):
            raise Exception('File not found: ' + local_path)

158
        return self.s.put(url, data=open(local_path, 'rb'), verify=False)
159

160
161
162
    #Full listing
    def get_figure_listing(self, report_id, chapter_id=None):
        chapter_filter = '/chapter/' + chapter_id if chapter_id else ''
163

164
165
166
        url = '{b}/report/{rpt}{chap}/figure?{p}'.format(
            b=self.base_url, rpt=report_id, chap=chapter_filter, p=urllib.urlencode({'all': '1'})
        )
167
        resp = self.s.get(url, verify=False)
168

169
170
171
172
        try:
            return [Figure(figure) for figure in resp.json()]
        except ValueError:
            raise Exception('Add a better exception string here')
173

174
175
    def get_figure(self, report_id, figure_id, chapter_id=None):
        chapter_filter = '/chapter/' + chapter_id if chapter_id else ''
176

177
178
        url = '{b}/report/{rpt}{chap}/figure/{fig}'.format(
            b=self.base_url, rpt=report_id, chap=chapter_filter, fig=figure_id
179
        )
180
        resp = self.s.get(url, params={'all': '1'}, verify=False)
181

182
183
184
185
        try:
            return Figure(resp.json())
        except ValueError:
            raise Exception(resp.text)
186

187
188
189
190
191
192
193
    @exists
    def figure_exists(self, report_id, figure_id, chapter_id=None):
        chapter_filter = '/chapter/' + chapter_id if chapter_id else ''

        url = '{b}/report/{rpt}{chap}/figure/{fig}?{p}'.format(
            b=self.base_url, rpt=report_id, chap=chapter_filter, fig=figure_id, p=urllib.urlencode({'all': '1'})
        )
194
        return self.s.head(url, verify=False)
195

196
197
    def get_image(self, image_id):
        url = '{b}/image/{img}'.format(b=self.base_url, img=image_id)
198
        resp = self.s.get(url, verify=False)
199

200
201
202
203
204
205
206
207
        try:
            return Image(resp.json())
        except ValueError:
            raise Exception(resp.text)

    @exists
    def image_exists(self, image_id):
        url = '{b}/image/{img}'.format(b=self.base_url, img=image_id)
208
        return self.s.head(url, verify=False)
209

210
211
    def has_all_associated_images(self, report_id, figure_id, target_image_ids):
        try:
212
213
214
215
            figure_image_ids = [i.identifier for i in self.get_figure(report_id, figure_id).images]
        except Exception, e:
            print e.message
            return False, set()
216

217
218
219
220
221
222
        target_set = set(target_image_ids)
        gcis_set = set(figure_image_ids)
        deltas = target_set - gcis_set

        if target_set.issubset(gcis_set):
            return True, deltas
223
        else:
224
            return False, deltas
225

226
227
    def test_login(self):
        url = '{b}/login.json'.format(b=self.base_url)
228
        resp = self.s.get(url, verify=False)
229
230
231
232
        return resp.status_code, resp.text

    def get_keyword_listing(self):
        url = '{b}/gcmd_keyword?{p}'.format(b=self.base_url, p=urllib.urlencode({'all': '1'}))
233
        resp = self.s.get(url, verify=False)
234

235
236
237
238
        return resp.json()

    def get_keyword(self, key_id):
        url = '{b}/gcmd_keyword/{k}'.format(b=self.base_url, k=key_id)
239
        return self.s.get(url, verify=False).json()
240
241
242

    def associate_keyword_with_figure(self, keyword_id, report_id, figure_id):
        url = '{b}/report/{rpt}/figure/keywords/{fig}'.format(b=self.base_url, rpt=report_id, fig=figure_id)
243
        return self.s.post(url, data=json.dumps({'identifier': keyword_id}), verify=False)
244
245
246

    def get_dataset(self, dataset_id):
        url = '{b}/dataset/{ds}'.format(b=self.base_url, ds=dataset_id)
247
        resp = self.s.get(url, verify=False)
248
249
250
        try:
            return Dataset(resp.json())
        except ValueError:
abuddenberg's avatar
abuddenberg committed
251
            raise Exception(resp.text)
252

253
254
255
    @exists
    def dataset_exists(self, dataset_id):
        url = '{b}/dataset/{ds}'.format(b=self.base_url, ds=dataset_id)
256
        return self.s.head(url, verify=False)
257

abuddenberg's avatar
abuddenberg committed
258
    def create_dataset(self, dataset):
259
        url = '{b}/dataset/'.format(b=self.base_url)
260
        return self.s.post(url, data=dataset.as_json(), verify=False)
261

262
263
    def update_dataset(self, dataset, old_id=None):
        url = '{b}/dataset/{ds}'.format(b=self.base_url, ds=old_id or dataset.identifier)
264
        return self.s.post(url, data=dataset.as_json(), verify=False)
265
266
267

    def delete_dataset(self, dataset):
        url = '{b}/dataset/{ds}'.format(b=self.base_url, ds=dataset.identifier)
268
        return self.s.delete(url, verify=False)
269

abuddenberg's avatar
abuddenberg committed
270
    def associate_dataset_with_image(self, dataset_id, image_id, activity_id=None):
271
        url = '{b}/image/prov/{img}'.format(b=self.base_url, img=image_id)
abuddenberg's avatar
abuddenberg committed
272

273
274
275
276
        data = {
            'parent_uri': '/dataset/' + dataset_id,
            'parent_rel': 'prov:wasDerivedFrom'
        }
abuddenberg's avatar
abuddenberg committed
277
278
279
        if activity_id:
            data['activity'] = activity_id

280
281
282
283
284
        try:
            self.delete_dataset_image_assoc(dataset_id, image_id)
        except AssociationException as e:
            print e.value

285
        resp = self.s.post(url, data=json.dumps(data), verify=False)
286
287
288
289

        if resp.status_code == 200:
            return resp
        else:
290
            raise Exception('Dataset association failed:\n{url}\n{resp}'.format(url=url, resp=resp.text))
291

abuddenberg's avatar
abuddenberg committed
292
293
294
295
296
297
298
299
300
    def delete_dataset_image_assoc(self, dataset_id, image_id):
        url = '{b}/image/prov/{img}'.format(b=self.base_url, img=image_id)

        data = {
            'delete': {
                'parent_uri': '/dataset/' + dataset_id,
                'parent_rel': 'prov:wasDerivedFrom'
            }
        }
301
        resp = self.s.post(url, data=json.dumps(data), verify=False)
abuddenberg's avatar
abuddenberg committed
302
303
304
305

        if resp.status_code == 200:
            return resp
        else:
306
307
308
309
310
311
            raise AssociationException(
                'Dataset dissociation failed:\n{url}\n{resp}\n{d}'.format(url=url, resp=resp.text, d=data))

    def create_or_update_dataset(self, dataset):
        if self.dataset_exists(dataset.identifier):
            print 'Updating dataset: ' + dataset.identifier
312
            self.update_dataset(dataset)
313
314
        else:
            print 'Creating dataset: ' + dataset.identifier
315
            self.create_dataset(dataset)
316

abuddenberg's avatar
abuddenberg committed
317
318
319
    # @exists
    def activity_exists(self, activity_id):
        url = '{b}/activity/{act}'.format(b=self.base_url, act=activity_id)
320
        resp = self.s.head(url, verify=False)
abuddenberg's avatar
abuddenberg committed
321
322
323
324
325
326
327
        if resp.status_code == 200:
            return True
        else:
            return False

    def get_activity(self, activity_id):
        url = '{b}/activity/{act}'.format(b=self.base_url, act=activity_id)
328
        resp = self.s.get(url, verify=False)
abuddenberg's avatar
abuddenberg committed
329
330
331
        try:
            return Activity(resp.json())
        except ValueError:
abuddenberg's avatar
abuddenberg committed
332
            raise Exception(resp.text)
abuddenberg's avatar
abuddenberg committed
333
334
335
336

    @http_resp
    def create_activity(self, activity):
        url = '{b}/activity/'.format(b=self.base_url)
337
        return self.s.post(url, data=activity.as_json(), verify=False)
abuddenberg's avatar
abuddenberg committed
338
339

    @http_resp
340
341
    def update_activity(self, activity, old_id=None):
        url = '{b}/activity/{act}'.format(b=self.base_url, act=old_id or activity.identifier)
342
        return self.s.post(url, data=activity.as_json(), verify=False)
abuddenberg's avatar
abuddenberg committed
343
344
345
346

    @http_resp
    def delete_activity(self, activity):
        url = '{b}/activity/{act}'.format(b=self.base_url, act=activity.identifier)
347
        return self.s.delete(url, verify=False)
abuddenberg's avatar
abuddenberg committed
348

349
350
351
352
353
    def create_or_update_activity(self, activity):
        if self.activity_exists(activity.identifier):
            self.update_activity(activity)
        else:
            self.create_activity(activity)
abuddenberg's avatar
abuddenberg committed
354
355
356
357

    @exists
    def person_exists(self, person_id):
        url = '{b}/person/{pid}'.format(b=self.base_url, pid=person_id)
358
        return self.s.head(url, verfiy=False)
abuddenberg's avatar
abuddenberg committed
359
360
361

    def get_person(self, person_id):
        url = '{b}/person/{pid}'.format(b=self.base_url, pid=person_id)
362
        resp = self.s.get(url, verify=False)
abuddenberg's avatar
abuddenberg committed
363
364
365
366
367
368
369
        try:
            return Person(resp.json())
        except ValueError:
            raise Exception(resp.text)

    def lookup_person(self, name):
        url = '{b}/autocomplete'.format(b=self.base_url)
370
        resp = self.s.get(url, params={'q': name, 'items': 15, 'type': 'person'}, verify=False)
abuddenberg's avatar
abuddenberg committed
371
372
373
374
375
376
377
378
379

        if resp.status_code == 200:
            return [re.match(r'\[person\] \{(\d+)\} (.*)', r).groups() for r in resp.json()]
        else:
            raise Exception(resp.text)

    @http_resp
    def create_person(self, person):
        url = '{b}/person/'.format(b=self.base_url)
380
        return self.s.post(url, data=person.as_json(), verify=False)
abuddenberg's avatar
abuddenberg committed
381
382

    @http_resp
383
384
    def update_person(self, person, old_id=None):
        url = '{b}/person/{pid}'.format(b=self.base_url, pid=old_id or person.identifier)
385
        return self.s.post(url, data=person.as_json(), verify=False)
abuddenberg's avatar
abuddenberg committed
386
387
388
389

    @http_resp
    def delete_person(self, person):
        url = '{b}/person/{pid}'.format(b=self.base_url, pid=person.identifier)
390
        return self.s.delete(url, verify=False)
abuddenberg's avatar
abuddenberg committed
391
392
393
394

    @exists
    def organization_exists(self, org_id):
        url = '{b}/organization/{org_id)'.format(b=self.base_url, org_id=org_id)
395
        return self.s.head(url, verify=False)
abuddenberg's avatar
abuddenberg committed
396
397

    def get_organization(self, org_id):
398
399
        url = '{b}/organization/{org_id}'.format(b=self.base_url, org_id=org_id)
        resp = self.s.get(url, verify=False)
abuddenberg's avatar
abuddenberg committed
400
401
402
403
404
405
406

        try:
            return Organization(resp.json())
        except ValueError:
            raise Exception(resp.text)

    def lookup_organization(self, name):
407
        url = '{b}/autocomplete'.format(b=self.base_url)
408
        resp = self.s.get(url, params={'q': name, 'items': 15, 'type': 'organization'}, verify=False)
409
410
411
412
413
        
        if resp.status_code == 200:
            return [re.match(r'\[organization\] \{(.*)\} (.*)', r).groups() for r in resp.json()]
        else:
            raise Exception(resp.text)
abuddenberg's avatar
abuddenberg committed
414
415
416
417

    @http_resp
    def create_organization(self, org):
        url = '{b}/organization/'.format(b=self.base_url)
418
        return self.s.post(url, data=org.as_json(), verify=False)
abuddenberg's avatar
abuddenberg committed
419
420

    @http_resp
421
422
    def update_organization(self, org, old_id=None):
        url = '{b}/organization/{org_id}'.format(b=self.base_url, org_id=old_id or org.identifier)
423
        return self.s.post(url, data=org.as_json(), verify=False)
abuddenberg's avatar
abuddenberg committed
424
425
426
427

    @http_resp
    def delete_organization(self, org):
        url = '{b}/organization/{org_id}'.format(b=self.base_url, org_id=org.identifier)
428
        return self.s.delete(url, verify=False)
429
430
431
432
433

    @http_resp
    def associate_contributor_with_figure(self, contrib, report_id, chapter_id, figure_id):
        url = '{b}/report/{rpt}/chapter/{chp}/figure/contributors/{fig}'.format(b=self.base_url, rpt=report_id, chp=chapter_id, fig=figure_id)

434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
        data = {
            'role': contrib.role.type_id,
        }

        if contrib.person is not None and contrib.person.id is not None:
            data['person_id'] = contrib.person.id
        if contrib.organization is not None and contrib.organization.identifier:
            data['organization_identifier'] = contrib.organization.identifier

        resp = self.s.post(url, data=json.dumps(data), verify=False)
        return resp

    @http_resp
    def delete_contributor_figure_assoc(self, contrib, report_id, chapter_id, figure_id):
        url = '{b}/report/{rpt}/chapter/{chp}/figure/contributors/{fig}'.format(b=self.base_url, rpt=report_id, chp=chapter_id, fig=figure_id)

        data = {
            'delete': {
                'role': contrib.role.type_id,
                'organization_identifier': contrib.organization.identifier,
                'person_id': contrib.person.identifier
            }
        }

        return self.s.post(url, data=json.dumps(data), verify=False)

    @http_resp
    def associate_contributor_with_image(self, contrib, image_id):
        url = '{b}/image/contributors/{img}'.format(b=self.base_url, img=image_id)

        data = {
            'role': contrib.role.type_id,
        }
        if contrib.person is not None and contrib.person.id is not None:
            data['person_id'] = contrib.person.id
        if contrib.organization is not None and contrib.organization.identifier:
            data['organization_identifier'] = contrib.organization.identifier

        return self.s.post(url, data=json.dumps(data), verify=False)

    @http_resp
    def delete_contributor_image_assoc(self, contrib, image_id):
        url = '{b}/image/contributors/{img}'.format(b=self.base_url, img=image_id)

        data = {
            'delete': {
                'role': contrib.role.type_id,
                'organization_identifier': contrib.organization.identifier,
                'person_id': contrib.person.identifier
            }
        }
485

486
        return self.s.post(url, data=json.dumps(data), verify=False)
487