arvados.api

Arvados API client

The code in this module builds Arvados API client objects you can use to submit Arvados API requests. This includes extending the underlying HTTP client with niceties such as caching, X-Request-Id header for tracking, and more. The main client constructors are api and api_from_config.

  1# Copyright (C) The Arvados Authors. All rights reserved.
  2#
  3# SPDX-License-Identifier: Apache-2.0
  4"""Arvados API client
  5
  6The code in this module builds Arvados API client objects you can use to submit
  7Arvados API requests. This includes extending the underlying HTTP client with
  8niceties such as caching, X-Request-Id header for tracking, and more. The main
  9client constructors are `api` and `api_from_config`.
 10"""
 11
 12import collections
 13import httplib2
 14import json
 15import logging
 16import os
 17import pathlib
 18import re
 19import socket
 20import ssl
 21import sys
 22import threading
 23import time
 24import types
 25
 26from typing import (
 27    Any,
 28    Dict,
 29    List,
 30    Mapping,
 31    Optional,
 32)
 33
 34import apiclient
 35import apiclient.http
 36from apiclient import discovery as apiclient_discovery
 37from apiclient import errors as apiclient_errors
 38from . import config
 39from . import errors
 40from . import retry
 41from . import util
 42from . import cache
 43from .logging import GoogleHTTPClientFilter, log_handler
 44
 45_logger = logging.getLogger('arvados.api')
 46_googleapiclient_log_lock = threading.Lock()
 47
 48MAX_IDLE_CONNECTION_DURATION = 30
 49"""
 50Number of seconds that API client HTTP connections should be allowed to idle
 51in keepalive state before they are forced closed. Client code can adjust this
 52constant, and it will be used for all Arvados API clients constructed after
 53that point.
 54"""
 55
 56# An unused HTTP 5xx status code to request a retry internally.
 57# See _intercept_http_request. This should not be user-visible.
 58_RETRY_4XX_STATUS = 545
 59
 60if sys.version_info >= (3,):
 61    httplib2.SSLHandshakeError = None
 62
 63_orig_retry_request = apiclient.http._retry_request
 64def _retry_request(http, num_retries, *args, **kwargs):
 65    try:
 66        num_retries = max(num_retries, http.num_retries)
 67    except AttributeError:
 68        # `http` client object does not have a `num_retries` attribute.
 69        # It apparently hasn't gone through _patch_http_request, possibly
 70        # because this isn't an Arvados API client. Pass through to
 71        # avoid interfering with other Google API clients.
 72        return _orig_retry_request(http, num_retries, *args, **kwargs)
 73    response, body = _orig_retry_request(http, num_retries, *args, **kwargs)
 74    # If _intercept_http_request ran out of retries for a 4xx response,
 75    # restore the original status code.
 76    if response.status == _RETRY_4XX_STATUS:
 77        response.status = int(response['status'])
 78    return (response, body)
 79apiclient.http._retry_request = _retry_request
 80
 81def _intercept_http_request(self, uri, method="GET", headers={}, **kwargs):
 82    if not headers.get('X-Request-Id'):
 83        headers['X-Request-Id'] = self._request_id()
 84    try:
 85        if (self.max_request_size and
 86            kwargs.get('body') and
 87            self.max_request_size < len(kwargs['body'])):
 88            raise apiclient_errors.MediaUploadSizeError("Request size %i bytes exceeds published limit of %i bytes" % (len(kwargs['body']), self.max_request_size))
 89
 90        headers['Authorization'] = 'OAuth2 %s' % self.arvados_api_token
 91
 92        if (time.time() - self._last_request_time) > self._max_keepalive_idle:
 93            # High probability of failure due to connection atrophy. Make
 94            # sure this request [re]opens a new connection by closing and
 95            # forgetting all cached connections first.
 96            for conn in self.connections.values():
 97                conn.close()
 98            self.connections.clear()
 99
100        self._last_request_time = time.time()
101        try:
102            response, body = self.orig_http_request(uri, method, headers=headers, **kwargs)
103        except ssl.SSLCertVerificationError as e:
104            raise ssl.SSLCertVerificationError(e.args[0], "Could not connect to %s\n%s\nPossible causes: remote SSL/TLS certificate expired, or was issued by an untrusted certificate authority." % (uri, e)) from None
105        # googleapiclient only retries 403, 429, and 5xx status codes.
106        # If we got another 4xx status that we want to retry, convert it into
107        # 5xx so googleapiclient handles it the way we want.
108        if response.status in retry._HTTP_CAN_RETRY and response.status < 500:
109            response.status = _RETRY_4XX_STATUS
110        return (response, body)
111    except Exception as e:
112        # Prepend "[request_id] " to the error message, which we
113        # assume is the first string argument passed to the exception
114        # constructor.
115        for i in range(len(e.args or ())):
116            if type(e.args[i]) == type(""):
117                e.args = e.args[:i] + ("[{}] {}".format(headers['X-Request-Id'], e.args[i]),) + e.args[i+1:]
118                raise type(e)(*e.args)
119        raise
120
121def _patch_http_request(http, api_token, num_retries):
122    http.arvados_api_token = api_token
123    http.max_request_size = 0
124    http.num_retries = num_retries
125    http.orig_http_request = http.request
126    http.request = types.MethodType(_intercept_http_request, http)
127    http._last_request_time = 0
128    http._max_keepalive_idle = MAX_IDLE_CONNECTION_DURATION
129    http._request_id = util.new_request_id
130    return http
131
132def _close_connections(self):
133    for conn in self._http.connections.values():
134        conn.close()
135
136# Monkey patch discovery._cast() so objects and arrays get serialized
137# with json.dumps() instead of str().
138_cast_orig = apiclient_discovery._cast
139def _cast_objects_too(value, schema_type):
140    global _cast_orig
141    if (type(value) != type('') and
142        type(value) != type(b'') and
143        (schema_type == 'object' or schema_type == 'array')):
144        return json.dumps(value)
145    else:
146        return _cast_orig(value, schema_type)
147apiclient_discovery._cast = _cast_objects_too
148
149# Convert apiclient's HttpErrors into our own API error subclass for better
150# error reporting.
151# Reassigning apiclient_errors.HttpError is not sufficient because most of the
152# apiclient submodules import the class into their own namespace.
153def _new_http_error(cls, *args, **kwargs):
154    return super(apiclient_errors.HttpError, cls).__new__(
155        errors.ApiError, *args, **kwargs)
156apiclient_errors.HttpError.__new__ = staticmethod(_new_http_error)
157
158def http_cache(data_type: str) -> cache.SafeHTTPCache:
159    """Set up an HTTP file cache
160
161    This function constructs and returns an `arvados.cache.SafeHTTPCache`
162    backed by the filesystem under `~/.cache/arvados/`, or `None` if the
163    directory cannot be set up. The return value can be passed to
164    `httplib2.Http` as the `cache` argument.
165
166    Arguments:
167
168    * data_type: str --- The name of the subdirectory under `~/.cache/arvados`
169      where data is cached.
170    """
171    try:
172        homedir = pathlib.Path.home()
173    except RuntimeError:
174        return None
175    path = pathlib.Path(homedir, '.cache', 'arvados', data_type)
176    try:
177        path.mkdir(parents=True, exist_ok=True)
178    except OSError:
179        return None
180    return cache.SafeHTTPCache(str(path), max_age=60*60*24*2)
181
182def api_client(
183        version: str,
184        discoveryServiceUrl: str,
185        token: str,
186        *,
187        cache: bool=True,
188        http: Optional[httplib2.Http]=None,
189        insecure: bool=False,
190        num_retries: int=10,
191        request_id: Optional[str]=None,
192        timeout: int=5*60,
193        **kwargs: Any,
194) -> apiclient_discovery.Resource:
195    """Build an Arvados API client
196
197    This function returns a `googleapiclient.discovery.Resource` object
198    constructed from the given arguments. This is a relatively low-level
199    interface that requires all the necessary inputs as arguments. Most
200    users will prefer to use `api` which can accept more flexible inputs.
201
202    Arguments:
203
204    * version: str --- A string naming the version of the Arvados API to use.
205
206    * discoveryServiceUrl: str --- The URL used to discover APIs passed
207      directly to `googleapiclient.discovery.build`.
208
209    * token: str --- The authentication token to send with each API call.
210
211    Keyword-only arguments:
212
213    * cache: bool --- If true, loads the API discovery document from, or
214      saves it to, a cache on disk (located at
215      `~/.cache/arvados/discovery`).
216
217    * http: httplib2.Http | None --- The HTTP client object the API client
218      object will use to make requests.  If not provided, this function will
219      build its own to use. Either way, the object will be patched as part
220      of the build process.
221
222    * insecure: bool --- If true, ignore SSL certificate validation
223      errors. Default `False`.
224
225    * num_retries: int --- The number of times to retry each API request if
226      it encounters a temporary failure. Default 10.
227
228    * request_id: str | None --- Default `X-Request-Id` header value for
229      outgoing requests that don't already provide one. If `None` or
230      omitted, generate a random ID. When retrying failed requests, the same
231      ID is used on all attempts.
232
233    * timeout: int --- A timeout value for HTTP requests in seconds. Default
234      300 (5 minutes).
235
236    Additional keyword arguments will be passed directly to
237    `googleapiclient.discovery.build`.
238    """
239    if http is None:
240        http = httplib2.Http(
241            ca_certs=util.ca_certs_path(),
242            cache=http_cache('discovery') if cache else None,
243            disable_ssl_certificate_validation=bool(insecure),
244        )
245    if http.timeout is None:
246        http.timeout = timeout
247    http = _patch_http_request(http, token, num_retries)
248
249    # The first time a client is instantiated, temporarily route
250    # googleapiclient.http retry logs if they're not already. These are
251    # important because temporary problems fetching the discovery document
252    # can cause clients to appear to hang early. This can be removed after
253    # we have a more general story for handling googleapiclient logs (#20521).
254    client_logger = logging.getLogger('googleapiclient.http')
255    # "first time a client is instantiated" = thread that acquires this lock
256    # It is never released.
257    # googleapiclient sets up its own NullHandler so we detect if logging is
258    # configured by looking for a real handler anywhere in the hierarchy.
259    client_logger_unconfigured = _googleapiclient_log_lock.acquire(blocking=False) and all(
260        isinstance(handler, logging.NullHandler)
261        for logger_name in ['', 'googleapiclient', 'googleapiclient.http']
262        for handler in logging.getLogger(logger_name).handlers
263    )
264    if client_logger_unconfigured:
265        client_level = client_logger.level
266        client_filter = GoogleHTTPClientFilter()
267        client_logger.addFilter(client_filter)
268        client_logger.addHandler(log_handler)
269        if logging.NOTSET < client_level < client_filter.retry_levelno:
270            client_logger.setLevel(client_level)
271        else:
272            client_logger.setLevel(client_filter.retry_levelno)
273    try:
274        svc = apiclient_discovery.build(
275            'arvados', version,
276            cache_discovery=False,
277            discoveryServiceUrl=discoveryServiceUrl,
278            http=http,
279            num_retries=num_retries,
280            **kwargs,
281        )
282    finally:
283        if client_logger_unconfigured:
284            client_logger.removeHandler(log_handler)
285            client_logger.removeFilter(client_filter)
286            client_logger.setLevel(client_level)
287    svc.api_token = token
288    svc.insecure = insecure
289    svc.request_id = request_id
290    svc.config = lambda: util.get_config_once(svc)
291    svc.vocabulary = lambda: util.get_vocabulary_once(svc)
292    svc.close_connections = types.MethodType(_close_connections, svc)
293    http.max_request_size = svc._rootDesc.get('maxRequestSize', 0)
294    http.cache = None
295    http._request_id = lambda: svc.request_id or util.new_request_id()
296    return svc
297
298def normalize_api_kwargs(
299        version: Optional[str]=None,
300        discoveryServiceUrl: Optional[str]=None,
301        host: Optional[str]=None,
302        token: Optional[str]=None,
303        **kwargs: Any,
304) -> Dict[str, Any]:
305    """Validate kwargs from `api` and build kwargs for `api_client`
306
307    This method takes high-level keyword arguments passed to the `api`
308    constructor and normalizes them into a new dictionary that can be passed
309    as keyword arguments to `api_client`. It raises `ValueError` if required
310    arguments are missing or conflict.
311
312    Arguments:
313
314    * version: str | None --- A string naming the version of the Arvados API
315      to use. If not specified, the code will log a warning and fall back to
316      'v1'.
317
318    * discoveryServiceUrl: str | None --- The URL used to discover APIs
319      passed directly to `googleapiclient.discovery.build`. It is an error
320      to pass both `discoveryServiceUrl` and `host`.
321
322    * host: str | None --- The hostname and optional port number of the
323      Arvados API server. Used to build `discoveryServiceUrl`. It is an
324      error to pass both `discoveryServiceUrl` and `host`.
325
326    * token: str --- The authentication token to send with each API call.
327
328    Additional keyword arguments will be included in the return value.
329    """
330    if discoveryServiceUrl and host:
331        raise ValueError("both discoveryServiceUrl and host provided")
332    elif discoveryServiceUrl:
333        url_src = "discoveryServiceUrl"
334    elif host:
335        url_src = "host argument"
336        discoveryServiceUrl = 'https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' % (host,)
337    elif token:
338        # This specific error message gets priority for backwards compatibility.
339        raise ValueError("token argument provided, but host missing.")
340    else:
341        raise ValueError("neither discoveryServiceUrl nor host provided")
342    if not token:
343        raise ValueError("%s provided, but token missing" % (url_src,))
344    if not version:
345        version = 'v1'
346        _logger.info(
347            "Using default API version. Call arvados.api(%r) instead.",
348            version,
349        )
350    return {
351        'discoveryServiceUrl': discoveryServiceUrl,
352        'token': token,
353        'version': version,
354        **kwargs,
355    }
356
357def api_kwargs_from_config(
358        version: Optional[str]=None,
359        apiconfig: Optional[Mapping[str, str]]=None,
360        **kwargs: Any
361) -> Dict[str, Any]:
362    """Build `api_client` keyword arguments from configuration
363
364    This function accepts a mapping with Arvados configuration settings like
365    `ARVADOS_API_HOST` and converts them into a mapping of keyword arguments
366    that can be passed to `api_client`. If `ARVADOS_API_HOST` or
367    `ARVADOS_API_TOKEN` are not configured, it raises `ValueError`.
368
369    Arguments:
370
371    * version: str | None --- A string naming the version of the Arvados API
372      to use. If not specified, the code will log a warning and fall back to
373      'v1'.
374
375    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
376      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
377      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
378      `arvados.config.settings` to get these parameters from user
379      configuration.
380
381    Additional keyword arguments will be included in the return value.
382    """
383    if apiconfig is None:
384        apiconfig = config.settings()
385    missing = " and ".join(
386        key
387        for key in ['ARVADOS_API_HOST', 'ARVADOS_API_TOKEN']
388        if key not in apiconfig
389    )
390    if missing:
391        raise ValueError(
392            "%s not set.\nPlease set in %s or export environment variable." %
393            (missing, config.default_config_file),
394        )
395    return normalize_api_kwargs(
396        version,
397        None,
398        apiconfig['ARVADOS_API_HOST'],
399        apiconfig['ARVADOS_API_TOKEN'],
400        insecure=config.flag_is_true('ARVADOS_API_HOST_INSECURE', apiconfig),
401        **kwargs,
402    )
403
404def api(
405        version: Optional[str]=None,
406        cache: bool=True,
407        host: Optional[str]=None,
408        token: Optional[str]=None,
409        insecure: bool=False,
410        request_id: Optional[str]=None,
411        timeout: int=5*60,
412        *,
413        discoveryServiceUrl: Optional[str]=None,
414        **kwargs: Any,
415) -> 'arvados.safeapi.ThreadSafeApiCache':
416    """Dynamically build an Arvados API client
417
418    This function provides a high-level "do what I mean" interface to build an
419    Arvados API client object. You can call it with no arguments to build a
420    client from user configuration; pass `host` and `token` arguments just
421    like you would write in user configuration; or pass additional arguments
422    for lower-level control over the client.
423
424    This function returns a `arvados.safeapi.ThreadSafeApiCache`, an
425    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
426    you're handling concurrency yourself and/or your application is very
427    performance-sensitive, consider calling `api_client` directly.
428
429    Arguments:
430
431    * version: str | None --- A string naming the version of the Arvados API
432      to use. If not specified, the code will log a warning and fall back to
433      'v1'.
434
435    * host: str | None --- The hostname and optional port number of the
436      Arvados API server.
437
438    * token: str | None --- The authentication token to send with each API
439      call.
440
441    * discoveryServiceUrl: str | None --- The URL used to discover APIs
442      passed directly to `googleapiclient.discovery.build`.
443
444    If `host`, `token`, and `discoveryServiceUrl` are all omitted, `host` and
445    `token` will be loaded from the user's configuration. Otherwise, you must
446    pass `token` and one of `host` or `discoveryServiceUrl`. It is an error to
447    pass both `host` and `discoveryServiceUrl`.
448
449    Other arguments are passed directly to `api_client`. See that function's
450    docstring for more information about their meaning.
451    """
452    kwargs.update(
453        cache=cache,
454        insecure=insecure,
455        request_id=request_id,
456        timeout=timeout,
457    )
458    if discoveryServiceUrl or host or token:
459        kwargs.update(normalize_api_kwargs(version, discoveryServiceUrl, host, token))
460    else:
461        kwargs.update(api_kwargs_from_config(version))
462    version = kwargs.pop('version')
463    # We do the import here to avoid a circular import at the top level.
464    from .safeapi import ThreadSafeApiCache
465    return ThreadSafeApiCache({}, {}, kwargs, version)
466
467def api_from_config(
468        version: Optional[str]=None,
469        apiconfig: Optional[Mapping[str, str]]=None,
470        **kwargs: Any
471) -> 'arvados.safeapi.ThreadSafeApiCache':
472    """Build an Arvados API client from a configuration mapping
473
474    This function builds an Arvados API client from a mapping with user
475    configuration. It accepts that mapping as an argument, so you can use a
476    configuration that's different from what the user has set up.
477
478    This function returns a `arvados.safeapi.ThreadSafeApiCache`, an
479    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
480    you're handling concurrency yourself and/or your application is very
481    performance-sensitive, consider calling `api_client` directly.
482
483    Arguments:
484
485    * version: str | None --- A string naming the version of the Arvados API
486      to use. If not specified, the code will log a warning and fall back to
487      'v1'.
488
489    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
490      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
491      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
492      `arvados.config.settings` to get these parameters from user
493      configuration.
494
495    Other arguments are passed directly to `api_client`. See that function's
496    docstring for more information about their meaning.
497    """
498    return api(**api_kwargs_from_config(version, apiconfig, **kwargs))
499
500class OrderedJsonModel(apiclient.model.JsonModel):
501    """Model class for JSON that preserves the contents' order
502
503    .. WARNING:: Deprecated
504       This model is redundant now that Python dictionaries preserve insertion
505       ordering. Code that passes this model to API constructors can remove it.
506
507    In Python versions before 3.6, API clients that cared about preserving the
508    order of fields in API server responses could use this model to do so.
509    Typical usage looked like:
510
511        from arvados.api import OrderedJsonModel
512        client = arvados.api('v1', ..., model=OrderedJsonModel())
513    """
514    @util._deprecated(preferred="the default model and rely on Python's built-in dictionary ordering")
515    def __init__(self, data_wrapper=False):
516        return super().__init__(data_wrapper)
517
518
519RETRY_DELAY_INITIAL = 0
520"""
521.. WARNING:: Deprecated
522   This constant was used by retry code in previous versions of the Arvados SDK.
523   Changing the value has no effect anymore.
524   Prefer passing `num_retries` to an API client constructor instead.
525   Refer to the constructor docstrings for details.
526"""
527
528RETRY_DELAY_BACKOFF = 0
529"""
530.. WARNING:: Deprecated
531   This constant was used by retry code in previous versions of the Arvados SDK.
532   Changing the value has no effect anymore.
533   Prefer passing `num_retries` to an API client constructor instead.
534   Refer to the constructor docstrings for details.
535"""
536
537RETRY_COUNT = 0
538"""
539.. WARNING:: Deprecated
540   This constant was used by retry code in previous versions of the Arvados SDK.
541   Changing the value has no effect anymore.
542   Prefer passing `num_retries` to an API client constructor instead.
543   Refer to the constructor docstrings for details.
544"""
MAX_IDLE_CONNECTION_DURATION = 30

Number of seconds that API client HTTP connections should be allowed to idle in keepalive state before they are forced closed. Client code can adjust this constant, and it will be used for all Arvados API clients constructed after that point.

def http_cache(data_type: str) -> arvados.cache.SafeHTTPCache:
159def http_cache(data_type: str) -> cache.SafeHTTPCache:
160    """Set up an HTTP file cache
161
162    This function constructs and returns an `arvados.cache.SafeHTTPCache`
163    backed by the filesystem under `~/.cache/arvados/`, or `None` if the
164    directory cannot be set up. The return value can be passed to
165    `httplib2.Http` as the `cache` argument.
166
167    Arguments:
168
169    * data_type: str --- The name of the subdirectory under `~/.cache/arvados`
170      where data is cached.
171    """
172    try:
173        homedir = pathlib.Path.home()
174    except RuntimeError:
175        return None
176    path = pathlib.Path(homedir, '.cache', 'arvados', data_type)
177    try:
178        path.mkdir(parents=True, exist_ok=True)
179    except OSError:
180        return None
181    return cache.SafeHTTPCache(str(path), max_age=60*60*24*2)

Set up an HTTP file cache

This function constructs and returns an arvados.cache.SafeHTTPCache backed by the filesystem under ~/.cache/arvados/, or None if the directory cannot be set up. The return value can be passed to httplib2.Http as the cache argument.

Arguments:

  • data_type: str — The name of the subdirectory under ~/.cache/arvados where data is cached.
def api_client( version: str, discoveryServiceUrl: str, token: str, *, cache: bool = True, http: Optional[httplib2.Http] = None, insecure: bool = False, num_retries: int = 10, request_id: Optional[str] = None, timeout: int = 300, **kwargs: Any) -> googleapiclient.discovery.Resource:
183def api_client(
184        version: str,
185        discoveryServiceUrl: str,
186        token: str,
187        *,
188        cache: bool=True,
189        http: Optional[httplib2.Http]=None,
190        insecure: bool=False,
191        num_retries: int=10,
192        request_id: Optional[str]=None,
193        timeout: int=5*60,
194        **kwargs: Any,
195) -> apiclient_discovery.Resource:
196    """Build an Arvados API client
197
198    This function returns a `googleapiclient.discovery.Resource` object
199    constructed from the given arguments. This is a relatively low-level
200    interface that requires all the necessary inputs as arguments. Most
201    users will prefer to use `api` which can accept more flexible inputs.
202
203    Arguments:
204
205    * version: str --- A string naming the version of the Arvados API to use.
206
207    * discoveryServiceUrl: str --- The URL used to discover APIs passed
208      directly to `googleapiclient.discovery.build`.
209
210    * token: str --- The authentication token to send with each API call.
211
212    Keyword-only arguments:
213
214    * cache: bool --- If true, loads the API discovery document from, or
215      saves it to, a cache on disk (located at
216      `~/.cache/arvados/discovery`).
217
218    * http: httplib2.Http | None --- The HTTP client object the API client
219      object will use to make requests.  If not provided, this function will
220      build its own to use. Either way, the object will be patched as part
221      of the build process.
222
223    * insecure: bool --- If true, ignore SSL certificate validation
224      errors. Default `False`.
225
226    * num_retries: int --- The number of times to retry each API request if
227      it encounters a temporary failure. Default 10.
228
229    * request_id: str | None --- Default `X-Request-Id` header value for
230      outgoing requests that don't already provide one. If `None` or
231      omitted, generate a random ID. When retrying failed requests, the same
232      ID is used on all attempts.
233
234    * timeout: int --- A timeout value for HTTP requests in seconds. Default
235      300 (5 minutes).
236
237    Additional keyword arguments will be passed directly to
238    `googleapiclient.discovery.build`.
239    """
240    if http is None:
241        http = httplib2.Http(
242            ca_certs=util.ca_certs_path(),
243            cache=http_cache('discovery') if cache else None,
244            disable_ssl_certificate_validation=bool(insecure),
245        )
246    if http.timeout is None:
247        http.timeout = timeout
248    http = _patch_http_request(http, token, num_retries)
249
250    # The first time a client is instantiated, temporarily route
251    # googleapiclient.http retry logs if they're not already. These are
252    # important because temporary problems fetching the discovery document
253    # can cause clients to appear to hang early. This can be removed after
254    # we have a more general story for handling googleapiclient logs (#20521).
255    client_logger = logging.getLogger('googleapiclient.http')
256    # "first time a client is instantiated" = thread that acquires this lock
257    # It is never released.
258    # googleapiclient sets up its own NullHandler so we detect if logging is
259    # configured by looking for a real handler anywhere in the hierarchy.
260    client_logger_unconfigured = _googleapiclient_log_lock.acquire(blocking=False) and all(
261        isinstance(handler, logging.NullHandler)
262        for logger_name in ['', 'googleapiclient', 'googleapiclient.http']
263        for handler in logging.getLogger(logger_name).handlers
264    )
265    if client_logger_unconfigured:
266        client_level = client_logger.level
267        client_filter = GoogleHTTPClientFilter()
268        client_logger.addFilter(client_filter)
269        client_logger.addHandler(log_handler)
270        if logging.NOTSET < client_level < client_filter.retry_levelno:
271            client_logger.setLevel(client_level)
272        else:
273            client_logger.setLevel(client_filter.retry_levelno)
274    try:
275        svc = apiclient_discovery.build(
276            'arvados', version,
277            cache_discovery=False,
278            discoveryServiceUrl=discoveryServiceUrl,
279            http=http,
280            num_retries=num_retries,
281            **kwargs,
282        )
283    finally:
284        if client_logger_unconfigured:
285            client_logger.removeHandler(log_handler)
286            client_logger.removeFilter(client_filter)
287            client_logger.setLevel(client_level)
288    svc.api_token = token
289    svc.insecure = insecure
290    svc.request_id = request_id
291    svc.config = lambda: util.get_config_once(svc)
292    svc.vocabulary = lambda: util.get_vocabulary_once(svc)
293    svc.close_connections = types.MethodType(_close_connections, svc)
294    http.max_request_size = svc._rootDesc.get('maxRequestSize', 0)
295    http.cache = None
296    http._request_id = lambda: svc.request_id or util.new_request_id()
297    return svc

Build an Arvados API client

This function returns a googleapiclient.discovery.Resource object constructed from the given arguments. This is a relatively low-level interface that requires all the necessary inputs as arguments. Most users will prefer to use api which can accept more flexible inputs.

Arguments:

  • version: str — A string naming the version of the Arvados API to use.

  • discoveryServiceUrl: str — The URL used to discover APIs passed directly to googleapiclient.discovery.build.

  • token: str — The authentication token to send with each API call.

Keyword-only arguments:

  • cache: bool — If true, loads the API discovery document from, or saves it to, a cache on disk (located at ~/.cache/arvados/discovery).

  • http: httplib2.Http | None — The HTTP client object the API client object will use to make requests. If not provided, this function will build its own to use. Either way, the object will be patched as part of the build process.

  • insecure: bool — If true, ignore SSL certificate validation errors. Default False.

  • num_retries: int — The number of times to retry each API request if it encounters a temporary failure. Default 10.

  • request_id: str | None — Default X-Request-Id header value for outgoing requests that don’t already provide one. If None or omitted, generate a random ID. When retrying failed requests, the same ID is used on all attempts.

  • timeout: int — A timeout value for HTTP requests in seconds. Default 300 (5 minutes).

Additional keyword arguments will be passed directly to googleapiclient.discovery.build.

def normalize_api_kwargs( version: Optional[str] = None, discoveryServiceUrl: Optional[str] = None, host: Optional[str] = None, token: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
299def normalize_api_kwargs(
300        version: Optional[str]=None,
301        discoveryServiceUrl: Optional[str]=None,
302        host: Optional[str]=None,
303        token: Optional[str]=None,
304        **kwargs: Any,
305) -> Dict[str, Any]:
306    """Validate kwargs from `api` and build kwargs for `api_client`
307
308    This method takes high-level keyword arguments passed to the `api`
309    constructor and normalizes them into a new dictionary that can be passed
310    as keyword arguments to `api_client`. It raises `ValueError` if required
311    arguments are missing or conflict.
312
313    Arguments:
314
315    * version: str | None --- A string naming the version of the Arvados API
316      to use. If not specified, the code will log a warning and fall back to
317      'v1'.
318
319    * discoveryServiceUrl: str | None --- The URL used to discover APIs
320      passed directly to `googleapiclient.discovery.build`. It is an error
321      to pass both `discoveryServiceUrl` and `host`.
322
323    * host: str | None --- The hostname and optional port number of the
324      Arvados API server. Used to build `discoveryServiceUrl`. It is an
325      error to pass both `discoveryServiceUrl` and `host`.
326
327    * token: str --- The authentication token to send with each API call.
328
329    Additional keyword arguments will be included in the return value.
330    """
331    if discoveryServiceUrl and host:
332        raise ValueError("both discoveryServiceUrl and host provided")
333    elif discoveryServiceUrl:
334        url_src = "discoveryServiceUrl"
335    elif host:
336        url_src = "host argument"
337        discoveryServiceUrl = 'https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' % (host,)
338    elif token:
339        # This specific error message gets priority for backwards compatibility.
340        raise ValueError("token argument provided, but host missing.")
341    else:
342        raise ValueError("neither discoveryServiceUrl nor host provided")
343    if not token:
344        raise ValueError("%s provided, but token missing" % (url_src,))
345    if not version:
346        version = 'v1'
347        _logger.info(
348            "Using default API version. Call arvados.api(%r) instead.",
349            version,
350        )
351    return {
352        'discoveryServiceUrl': discoveryServiceUrl,
353        'token': token,
354        'version': version,
355        **kwargs,
356    }

Validate kwargs from api and build kwargs for api_client

This method takes high-level keyword arguments passed to the api constructor and normalizes them into a new dictionary that can be passed as keyword arguments to api_client. It raises ValueError if required arguments are missing or conflict.

Arguments:

  • version: str | None — A string naming the version of the Arvados API to use. If not specified, the code will log a warning and fall back to ‘v1’.

  • discoveryServiceUrl: str | None — The URL used to discover APIs passed directly to googleapiclient.discovery.build. It is an error to pass both discoveryServiceUrl and host.

  • host: str | None — The hostname and optional port number of the Arvados API server. Used to build discoveryServiceUrl. It is an error to pass both discoveryServiceUrl and host.

  • token: str — The authentication token to send with each API call.

Additional keyword arguments will be included in the return value.

def api_kwargs_from_config( version: Optional[str] = None, apiconfig: Optional[Mapping[str, str]] = None, **kwargs: Any) -> Dict[str, Any]:
358def api_kwargs_from_config(
359        version: Optional[str]=None,
360        apiconfig: Optional[Mapping[str, str]]=None,
361        **kwargs: Any
362) -> Dict[str, Any]:
363    """Build `api_client` keyword arguments from configuration
364
365    This function accepts a mapping with Arvados configuration settings like
366    `ARVADOS_API_HOST` and converts them into a mapping of keyword arguments
367    that can be passed to `api_client`. If `ARVADOS_API_HOST` or
368    `ARVADOS_API_TOKEN` are not configured, it raises `ValueError`.
369
370    Arguments:
371
372    * version: str | None --- A string naming the version of the Arvados API
373      to use. If not specified, the code will log a warning and fall back to
374      'v1'.
375
376    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
377      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
378      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
379      `arvados.config.settings` to get these parameters from user
380      configuration.
381
382    Additional keyword arguments will be included in the return value.
383    """
384    if apiconfig is None:
385        apiconfig = config.settings()
386    missing = " and ".join(
387        key
388        for key in ['ARVADOS_API_HOST', 'ARVADOS_API_TOKEN']
389        if key not in apiconfig
390    )
391    if missing:
392        raise ValueError(
393            "%s not set.\nPlease set in %s or export environment variable." %
394            (missing, config.default_config_file),
395        )
396    return normalize_api_kwargs(
397        version,
398        None,
399        apiconfig['ARVADOS_API_HOST'],
400        apiconfig['ARVADOS_API_TOKEN'],
401        insecure=config.flag_is_true('ARVADOS_API_HOST_INSECURE', apiconfig),
402        **kwargs,
403    )

Build api_client keyword arguments from configuration

This function accepts a mapping with Arvados configuration settings like ARVADOS_API_HOST and converts them into a mapping of keyword arguments that can be passed to api_client. If ARVADOS_API_HOST or ARVADOS_API_TOKEN are not configured, it raises ValueError.

Arguments:

  • version: str | None — A string naming the version of the Arvados API to use. If not specified, the code will log a warning and fall back to ‘v1’.

  • apiconfig: Mapping[str, str] | None — A mapping with entries for ARVADOS_API_HOST, ARVADOS_API_TOKEN, and optionally ARVADOS_API_HOST_INSECURE. If not provided, calls arvados.config.settings to get these parameters from user configuration.

Additional keyword arguments will be included in the return value.

def api( version: Optional[str] = None, cache: bool = True, host: Optional[str] = None, token: Optional[str] = None, insecure: bool = False, request_id: Optional[str] = None, timeout: int = 300, *, discoveryServiceUrl: Optional[str] = None, **kwargs: Any) -> arvados.safeapi.ThreadSafeApiCache:
405def api(
406        version: Optional[str]=None,
407        cache: bool=True,
408        host: Optional[str]=None,
409        token: Optional[str]=None,
410        insecure: bool=False,
411        request_id: Optional[str]=None,
412        timeout: int=5*60,
413        *,
414        discoveryServiceUrl: Optional[str]=None,
415        **kwargs: Any,
416) -> 'arvados.safeapi.ThreadSafeApiCache':
417    """Dynamically build an Arvados API client
418
419    This function provides a high-level "do what I mean" interface to build an
420    Arvados API client object. You can call it with no arguments to build a
421    client from user configuration; pass `host` and `token` arguments just
422    like you would write in user configuration; or pass additional arguments
423    for lower-level control over the client.
424
425    This function returns a `arvados.safeapi.ThreadSafeApiCache`, an
426    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
427    you're handling concurrency yourself and/or your application is very
428    performance-sensitive, consider calling `api_client` directly.
429
430    Arguments:
431
432    * version: str | None --- A string naming the version of the Arvados API
433      to use. If not specified, the code will log a warning and fall back to
434      'v1'.
435
436    * host: str | None --- The hostname and optional port number of the
437      Arvados API server.
438
439    * token: str | None --- The authentication token to send with each API
440      call.
441
442    * discoveryServiceUrl: str | None --- The URL used to discover APIs
443      passed directly to `googleapiclient.discovery.build`.
444
445    If `host`, `token`, and `discoveryServiceUrl` are all omitted, `host` and
446    `token` will be loaded from the user's configuration. Otherwise, you must
447    pass `token` and one of `host` or `discoveryServiceUrl`. It is an error to
448    pass both `host` and `discoveryServiceUrl`.
449
450    Other arguments are passed directly to `api_client`. See that function's
451    docstring for more information about their meaning.
452    """
453    kwargs.update(
454        cache=cache,
455        insecure=insecure,
456        request_id=request_id,
457        timeout=timeout,
458    )
459    if discoveryServiceUrl or host or token:
460        kwargs.update(normalize_api_kwargs(version, discoveryServiceUrl, host, token))
461    else:
462        kwargs.update(api_kwargs_from_config(version))
463    version = kwargs.pop('version')
464    # We do the import here to avoid a circular import at the top level.
465    from .safeapi import ThreadSafeApiCache
466    return ThreadSafeApiCache({}, {}, kwargs, version)

Dynamically build an Arvados API client

This function provides a high-level “do what I mean” interface to build an Arvados API client object. You can call it with no arguments to build a client from user configuration; pass host and token arguments just like you would write in user configuration; or pass additional arguments for lower-level control over the client.

This function returns a arvados.safeapi.ThreadSafeApiCache, an API-compatible wrapper around googleapiclient.discovery.Resource. If you’re handling concurrency yourself and/or your application is very performance-sensitive, consider calling api_client directly.

Arguments:

  • version: str | None — A string naming the version of the Arvados API to use. If not specified, the code will log a warning and fall back to ‘v1’.

  • host: str | None — The hostname and optional port number of the Arvados API server.

  • token: str | None — The authentication token to send with each API call.

  • discoveryServiceUrl: str | None — The URL used to discover APIs passed directly to googleapiclient.discovery.build.

If host, token, and discoveryServiceUrl are all omitted, host and token will be loaded from the user’s configuration. Otherwise, you must pass token and one of host or discoveryServiceUrl. It is an error to pass both host and discoveryServiceUrl.

Other arguments are passed directly to api_client. See that function’s docstring for more information about their meaning.

def api_from_config( version: Optional[str] = None, apiconfig: Optional[Mapping[str, str]] = None, **kwargs: Any) -> arvados.safeapi.ThreadSafeApiCache:
468def api_from_config(
469        version: Optional[str]=None,
470        apiconfig: Optional[Mapping[str, str]]=None,
471        **kwargs: Any
472) -> 'arvados.safeapi.ThreadSafeApiCache':
473    """Build an Arvados API client from a configuration mapping
474
475    This function builds an Arvados API client from a mapping with user
476    configuration. It accepts that mapping as an argument, so you can use a
477    configuration that's different from what the user has set up.
478
479    This function returns a `arvados.safeapi.ThreadSafeApiCache`, an
480    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
481    you're handling concurrency yourself and/or your application is very
482    performance-sensitive, consider calling `api_client` directly.
483
484    Arguments:
485
486    * version: str | None --- A string naming the version of the Arvados API
487      to use. If not specified, the code will log a warning and fall back to
488      'v1'.
489
490    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
491      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
492      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
493      `arvados.config.settings` to get these parameters from user
494      configuration.
495
496    Other arguments are passed directly to `api_client`. See that function's
497    docstring for more information about their meaning.
498    """
499    return api(**api_kwargs_from_config(version, apiconfig, **kwargs))

Build an Arvados API client from a configuration mapping

This function builds an Arvados API client from a mapping with user configuration. It accepts that mapping as an argument, so you can use a configuration that’s different from what the user has set up.

This function returns a arvados.safeapi.ThreadSafeApiCache, an API-compatible wrapper around googleapiclient.discovery.Resource. If you’re handling concurrency yourself and/or your application is very performance-sensitive, consider calling api_client directly.

Arguments:

  • version: str | None — A string naming the version of the Arvados API to use. If not specified, the code will log a warning and fall back to ‘v1’.

  • apiconfig: Mapping[str, str] | None — A mapping with entries for ARVADOS_API_HOST, ARVADOS_API_TOKEN, and optionally ARVADOS_API_HOST_INSECURE. If not provided, calls arvados.config.settings to get these parameters from user configuration.

Other arguments are passed directly to api_client. See that function’s docstring for more information about their meaning.

class OrderedJsonModel(googleapiclient.model.JsonModel):
501class OrderedJsonModel(apiclient.model.JsonModel):
502    """Model class for JSON that preserves the contents' order
503
504    .. WARNING:: Deprecated
505       This model is redundant now that Python dictionaries preserve insertion
506       ordering. Code that passes this model to API constructors can remove it.
507
508    In Python versions before 3.6, API clients that cared about preserving the
509    order of fields in API server responses could use this model to do so.
510    Typical usage looked like:
511
512        from arvados.api import OrderedJsonModel
513        client = arvados.api('v1', ..., model=OrderedJsonModel())
514    """
515    @util._deprecated(preferred="the default model and rely on Python's built-in dictionary ordering")
516    def __init__(self, data_wrapper=False):
517        return super().__init__(data_wrapper)

Model class for JSON that preserves the contents’ order

In Python versions before 3.6, API clients that cared about preserving the order of fields in API server responses could use this model to do so. Typical usage looked like:

from arvados.api import OrderedJsonModel
client = arvados.api('v1', ..., model=OrderedJsonModel())
@util._deprecated(preferred="the default model and rely on Python's built-in dictionary ordering")
OrderedJsonModel(data_wrapper=False)
515    @util._deprecated(preferred="the default model and rely on Python's built-in dictionary ordering")
516    def __init__(self, data_wrapper=False):
517        return super().__init__(data_wrapper)
Inherited Members
googleapiclient.model.JsonModel
accept
content_type
alt_param
serialize
deserialize
no_content_response
googleapiclient.model.BaseModel
request
response
RETRY_DELAY_INITIAL = 0
RETRY_DELAY_BACKOFF = 0
RETRY_COUNT = 0