Merge branch 'master' into 3076-topnav-help
[arvados.git] / sdk / python / arvados / api.py
1 import httplib2
2 import json
3 import logging
4 import os
5 import re
6 import types
7
8 import apiclient
9 import apiclient.discovery
10 import config
11 import errors
12 import util
13
14 services = {}
15
16 class CredentialsFromEnv(object):
17     @staticmethod
18     def http_request(self, uri, **kwargs):
19         from httplib import BadStatusLine
20         if 'headers' not in kwargs:
21             kwargs['headers'] = {}
22
23         if config.get("ARVADOS_EXTERNAL_CLIENT", "") == "true":
24             kwargs['headers']['X-External-Client'] = '1'
25
26         kwargs['headers']['Authorization'] = 'OAuth2 %s' % config.get('ARVADOS_API_TOKEN', 'ARVADOS_API_TOKEN_not_set')
27         try:
28             return self.orig_http_request(uri, **kwargs)
29         except BadStatusLine:
30             # This is how httplib tells us that it tried to reuse an
31             # existing connection but it was already closed by the
32             # server. In that case, yes, we would like to retry.
33             # Unfortunately, we are not absolutely certain that the
34             # previous call did not succeed, so this is slightly
35             # risky.
36             return self.orig_http_request(uri, **kwargs)
37     def authorize(self, http):
38         http.orig_http_request = http.request
39         http.request = types.MethodType(self.http_request, http)
40         return http
41
42 # Monkey patch discovery._cast() so objects and arrays get serialized
43 # with json.dumps() instead of str().
44 _cast_orig = apiclient.discovery._cast
45 def _cast_objects_too(value, schema_type):
46     global _cast_orig
47     if (type(value) != type('') and
48         (schema_type == 'object' or schema_type == 'array')):
49         return json.dumps(value)
50     else:
51         return _cast_orig(value, schema_type)
52 apiclient.discovery._cast = _cast_objects_too
53
54 def http_cache(data_type):
55     path = os.environ['HOME'] + '/.cache/arvados/' + data_type
56     try:
57         util.mkdir_dash_p(path)
58     except OSError:
59         path = None
60     return path
61
62 def api(version=None, cache=True):
63     global services
64
65     if 'ARVADOS_DEBUG' in config.settings():
66         logging.basicConfig(level=logging.DEBUG)
67
68     if not cache or not services.get(version):
69         apiVersion = version
70         if not version:
71             apiVersion = 'v1'
72             logging.info("Using default API version. " +
73                          "Call arvados.api('%s') instead." %
74                          apiVersion)
75         if 'ARVADOS_API_HOST' not in config.settings():
76             raise Exception("ARVADOS_API_HOST is not set. Aborting.")
77         url = ('https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' %
78                config.get('ARVADOS_API_HOST'))
79         credentials = CredentialsFromEnv()
80
81         # Use system's CA certificates (if we find them) instead of httplib2's
82         ca_certs = '/etc/ssl/certs/ca-certificates.crt'
83         if not os.path.exists(ca_certs):
84             ca_certs = None             # use httplib2 default
85
86         http = httplib2.Http(ca_certs=ca_certs,
87                              cache=(http_cache('discovery') if cache else None))
88         http = credentials.authorize(http)
89         if re.match(r'(?i)^(true|1|yes)$',
90                     config.get('ARVADOS_API_HOST_INSECURE', 'no')):
91             http.disable_ssl_certificate_validation=True
92         services[version] = apiclient.discovery.build(
93             'arvados', apiVersion, http=http, discoveryServiceUrl=url)
94         http.cache = None
95     return services[version]