arvados.api

Arvados REST API client

This module provides classes and functions to construct an Arvados REST API client. Most users will want to use one of these constructor functions, in order of preference:

  • arvados.api.api provides a high-level interface to construct a client from either arguments or user configuration. You can call this module just like a function as a shortcut for calling arvados.api.api.

  • arvados.api.api_from_config constructs a client from user configuration in a dictionary.

  • arvados.api.api_client provides a lower-level interface to construct a simpler client object that is not threadsafe.

Other classes and functions is this module support creating and customizing the client for specialized use-cases.

The methods on an Arvados REST API client are generated dynamically at runtime. The arvados.api_resources module documents those methods and return values for the current version of Arvados. It does not implement anything so you don’t need to import it, but it’s a helpful reference to understand how to use the Arvados REST API client.

  1# Copyright (C) The Arvados Authors. All rights reserved.
  2#
  3# SPDX-License-Identifier: Apache-2.0
  4"""Arvados REST API client
  5
  6This module provides classes and functions to construct an Arvados REST API
  7client. Most users will want to use one of these constructor functions, in
  8order of preference:
  9
 10* `arvados.api.api` provides a high-level interface to construct a client from
 11  either arguments or user configuration. You can call this module just like
 12  a function as a shortcut for calling `arvados.api.api`.
 13
 14* `arvados.api.api_from_config` constructs a client from user configuration in
 15  a dictionary.
 16
 17* `arvados.api.api_client` provides a lower-level interface to construct a
 18  simpler client object that is not threadsafe.
 19
 20Other classes and functions is this module support creating and customizing
 21the client for specialized use-cases.
 22
 23The methods on an Arvados REST API client are generated dynamically at
 24runtime. The `arvados.api_resources` module documents those methods and
 25return values for the current version of Arvados. It does not
 26implement anything so you don't need to import it, but it's a helpful
 27reference to understand how to use the Arvados REST API client.
 28"""
 29
 30import collections
 31import errno
 32import hashlib
 33import httplib2
 34import json
 35import logging
 36import os
 37import pathlib
 38import re
 39import socket
 40import ssl
 41import sys
 42import tempfile
 43import threading
 44import time
 45import types
 46
 47from typing import (
 48    Any,
 49    Dict,
 50    List,
 51    Mapping,
 52    Optional,
 53)
 54
 55import apiclient
 56import apiclient.http
 57from apiclient import discovery as apiclient_discovery
 58from apiclient import errors as apiclient_errors
 59from . import config
 60from . import errors
 61from . import keep
 62from . import retry
 63from . import util
 64from ._internal import basedirs
 65from .logging import GoogleHTTPClientFilter, log_handler
 66
 67_logger = logging.getLogger('arvados.api')
 68_googleapiclient_log_lock = threading.Lock()
 69
 70MAX_IDLE_CONNECTION_DURATION = 30
 71"""
 72Number of seconds that API client HTTP connections should be allowed to idle
 73in keepalive state before they are forced closed. Client code can adjust this
 74constant, and it will be used for all Arvados API clients constructed after
 75that point.
 76"""
 77
 78# An unused HTTP 5xx status code to request a retry internally.
 79# See _intercept_http_request. This should not be user-visible.
 80_RETRY_4XX_STATUS = 545
 81
 82if sys.version_info >= (3,):
 83    httplib2.SSLHandshakeError = None
 84
 85def _reset_googleapiclient_logging() -> None:
 86    """Make the next API client constructor log googleapiclient retries
 87
 88    This function is for main-level CLI tools like arv-copy to ensure retries
 89    are logged when constructing different API clients.
 90    """
 91    try:
 92        _googleapiclient_log_lock.release()
 93    except RuntimeError:
 94        pass
 95
 96_orig_retry_request = apiclient.http._retry_request
 97def _retry_request(http, num_retries, *args, **kwargs):
 98    try:
 99        num_retries = max(num_retries, http.num_retries)
100    except AttributeError:
101        # `http` client object does not have a `num_retries` attribute.
102        # It apparently hasn't gone through _patch_http_request, possibly
103        # because this isn't an Arvados API client. Pass through to
104        # avoid interfering with other Google API clients.
105        return _orig_retry_request(http, num_retries, *args, **kwargs)
106    response, body = _orig_retry_request(http, num_retries, *args, **kwargs)
107    # If _intercept_http_request ran out of retries for a 4xx response,
108    # restore the original status code.
109    if response.status == _RETRY_4XX_STATUS:
110        response.status = int(response['status'])
111    return (response, body)
112apiclient.http._retry_request = _retry_request
113
114def _intercept_http_request(self, uri, method="GET", headers={}, **kwargs):
115    if not headers.get('X-Request-Id'):
116        headers['X-Request-Id'] = self._request_id()
117    try:
118        if (self.max_request_size and
119            kwargs.get('body') and
120            self.max_request_size < len(kwargs['body'])):
121            raise apiclient_errors.MediaUploadSizeError("Request size %i bytes exceeds published limit of %i bytes" % (len(kwargs['body']), self.max_request_size))
122
123        headers['Authorization'] = 'Bearer %s' % self.arvados_api_token
124
125        if (time.time() - self._last_request_time) > self._max_keepalive_idle:
126            # High probability of failure due to connection atrophy. Make
127            # sure this request [re]opens a new connection by closing and
128            # forgetting all cached connections first.
129            for conn in self.connections.values():
130                conn.close()
131            self.connections.clear()
132
133        self._last_request_time = time.time()
134        try:
135            response, body = self.orig_http_request(uri, method, headers=headers, **kwargs)
136        except ssl.CertificateError as e:
137            raise ssl.CertificateError(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
138        # googleapiclient only retries 403, 429, and 5xx status codes.
139        # If we got another 4xx status that we want to retry, convert it into
140        # 5xx so googleapiclient handles it the way we want.
141        if response.status in retry._HTTP_CAN_RETRY and response.status < 500:
142            response.status = _RETRY_4XX_STATUS
143        return (response, body)
144    except Exception as e:
145        # Prepend "[request_id] " to the error message, which we
146        # assume is the first string argument passed to the exception
147        # constructor.
148        for i in range(len(e.args or ())):
149            if type(e.args[i]) == type(""):
150                e.args = e.args[:i] + ("[{}] {}".format(headers['X-Request-Id'], e.args[i]),) + e.args[i+1:]
151                raise type(e)(*e.args)
152        raise
153
154def _patch_http_request(http, api_token, num_retries):
155    http.arvados_api_token = api_token
156    http.max_request_size = 0
157    http.num_retries = num_retries
158    http.orig_http_request = http.request
159    http.request = types.MethodType(_intercept_http_request, http)
160    http._last_request_time = 0
161    http._max_keepalive_idle = MAX_IDLE_CONNECTION_DURATION
162    http._request_id = util.new_request_id
163    return http
164
165def _close_connections(self):
166    for conn in self._http.connections.values():
167        conn.close()
168
169# Monkey patch discovery._cast() so objects and arrays get serialized
170# with json.dumps() instead of str().
171_cast_orig = apiclient_discovery._cast
172def _cast_objects_too(value, schema_type):
173    global _cast_orig
174    if (type(value) != type('') and
175        type(value) != type(b'') and
176        (schema_type == 'object' or schema_type == 'array')):
177        return json.dumps(value)
178    else:
179        return _cast_orig(value, schema_type)
180apiclient_discovery._cast = _cast_objects_too
181
182# Convert apiclient's HttpErrors into our own API error subclass for better
183# error reporting.
184# Reassigning apiclient_errors.HttpError is not sufficient because most of the
185# apiclient submodules import the class into their own namespace.
186def _new_http_error(cls, *args, **kwargs):
187    return super(apiclient_errors.HttpError, cls).__new__(
188        errors.ApiError, *args, **kwargs)
189apiclient_errors.HttpError.__new__ = staticmethod(_new_http_error)
190
191class ThreadSafeHTTPCache:
192    """Thread-safe replacement for `httplib2.FileCache`
193
194    `arvados.api.http_cache` is the preferred way to construct this object.
195    Refer to that function's docstring for details.
196    """
197
198    def __init__(self, path=None, max_age=None):
199        self._dir = path
200        if max_age is not None:
201            try:
202                self._clean(threshold=time.time() - max_age)
203            except:
204                pass
205
206    def _clean(self, threshold=0):
207        for ent in os.listdir(self._dir):
208            fnm = os.path.join(self._dir, ent)
209            if os.path.isdir(fnm) or not fnm.endswith('.tmp'):
210                continue
211            stat = os.lstat(fnm)
212            if stat.st_mtime < threshold:
213                try:
214                    os.unlink(fnm)
215                except OSError as err:
216                    if err.errno != errno.ENOENT:
217                        raise
218
219    def __str__(self):
220        return self._dir
221
222    def _filename(self, url):
223        return os.path.join(self._dir, hashlib.md5(url.encode('utf-8')).hexdigest()+'.tmp')
224
225    def get(self, url):
226        filename = self._filename(url)
227        try:
228            with open(filename, 'rb') as f:
229                return f.read()
230        except (IOError, OSError):
231            return None
232
233    def set(self, url, content):
234        try:
235            fd, tempname = tempfile.mkstemp(dir=self._dir)
236        except:
237            return None
238        try:
239            try:
240                f = os.fdopen(fd, 'wb')
241            except:
242                os.close(fd)
243                raise
244            try:
245                f.write(content)
246            finally:
247                f.close()
248            os.rename(tempname, self._filename(url))
249            tempname = None
250        finally:
251            if tempname:
252                os.unlink(tempname)
253
254    def delete(self, url):
255        try:
256            os.unlink(self._filename(url))
257        except OSError as err:
258            if err.errno != errno.ENOENT:
259                raise
260
261
262class ThreadSafeAPIClient(object):
263    """Thread-safe wrapper for an Arvados API client
264
265    This class takes all the arguments necessary to build a lower-level
266    Arvados API client `googleapiclient.discovery.Resource`, then
267    transparently builds and wraps a unique object per thread. This works
268    around the fact that the client's underlying HTTP client object is not
269    thread-safe.
270
271    Arguments:
272
273    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
274      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
275      `ARVADOS_API_HOST_INSECURE`. If not provided, uses
276      `arvados.config.settings` to get these parameters from user
277      configuration.  You can pass an empty mapping to build the client
278      solely from `api_params`.
279
280    * keep_params: Mapping[str, Any] --- Keyword arguments used to construct
281      an associated `arvados.keep.KeepClient`.
282
283    * api_params: Mapping[str, Any] --- Keyword arguments used to construct
284      each thread's API client. These have the same meaning as in the
285      `arvados.api.api` function.
286
287    * version: str | None --- A string naming the version of the Arvados API
288      to use. If not specified, the code will log a warning and fall back to
289      `'v1'`.
290    """
291    def __init__(
292            self,
293            apiconfig: Optional[Mapping[str, str]]=None,
294            keep_params: Optional[Mapping[str, Any]]={},
295            api_params: Optional[Mapping[str, Any]]={},
296            version: Optional[str]=None,
297    ) -> None:
298        if apiconfig or apiconfig is None:
299            self._api_kwargs = api_kwargs_from_config(version, apiconfig, **api_params)
300        else:
301            self._api_kwargs = normalize_api_kwargs(version, **api_params)
302        self.api_token = self._api_kwargs['token']
303        self.request_id = self._api_kwargs.get('request_id')
304        self.local = threading.local()
305        self.keep = keep.KeepClient(api_client=self, **keep_params)
306
307    def localapi(self) -> 'googleapiclient.discovery.Resource':
308        try:
309            client = self.local.api
310        except AttributeError:
311            client = api_client(**self._api_kwargs)
312            client._http._request_id = lambda: self.request_id or util.new_request_id()
313            self.local.api = client
314        return client
315
316    def __getattr__(self, name: str) -> Any:
317        # Proxy nonexistent attributes to the thread-local API client.
318        return getattr(self.localapi(), name)
319
320
321def http_cache(data_type: str) -> Optional[ThreadSafeHTTPCache]:
322    """Set up an HTTP file cache
323
324    This function constructs and returns an `arvados.api.ThreadSafeHTTPCache`
325    backed by the filesystem under a cache directory from the environment, or
326    `None` if the directory cannot be set up. The return value can be passed to
327    `httplib2.Http` as the `cache` argument.
328
329    Arguments:
330
331    * data_type: str --- The name of the subdirectory
332      where data is cached.
333    """
334    try:
335        path = basedirs.BaseDirectories('CACHE').storage_path(data_type)
336    except (OSError, RuntimeError):
337        return None
338    else:
339        return ThreadSafeHTTPCache(str(path), max_age=60*60*24*2)
340
341def api_client(
342        version: str,
343        discoveryServiceUrl: str,
344        token: str,
345        *,
346        cache: bool=True,
347        http: Optional[httplib2.Http]=None,
348        insecure: bool=False,
349        num_retries: int=10,
350        request_id: Optional[str]=None,
351        timeout: int=5*60,
352        **kwargs: Any,
353) -> apiclient_discovery.Resource:
354    """Build an Arvados API client
355
356    This function returns a `googleapiclient.discovery.Resource` object
357    constructed from the given arguments. This is a relatively low-level
358    interface that requires all the necessary inputs as arguments. Most
359    users will prefer to use `api` which can accept more flexible inputs.
360
361    Arguments:
362
363    * version: str --- A string naming the version of the Arvados API to use.
364
365    * discoveryServiceUrl: str --- The URL used to discover APIs passed
366      directly to `googleapiclient.discovery.build`.
367
368    * token: str --- The authentication token to send with each API call.
369
370    Keyword-only arguments:
371
372    * cache: bool --- If true, loads the API discovery document from, or
373      saves it to, a cache on disk.
374
375    * http: httplib2.Http | None --- The HTTP client object the API client
376      object will use to make requests.  If not provided, this function will
377      build its own to use. Either way, the object will be patched as part
378      of the build process.
379
380    * insecure: bool --- If true, ignore SSL certificate validation
381      errors. Default `False`.
382
383    * num_retries: int --- The number of times to retry each API request if
384      it encounters a temporary failure. Default 10.
385
386    * request_id: str | None --- Default `X-Request-Id` header value for
387      outgoing requests that don't already provide one. If `None` or
388      omitted, generate a random ID. When retrying failed requests, the same
389      ID is used on all attempts.
390
391    * timeout: int --- A timeout value for HTTP requests in seconds. Default
392      300 (5 minutes).
393
394    Additional keyword arguments will be passed directly to
395    `googleapiclient.discovery.build`.
396    """
397    if http is None:
398        http = httplib2.Http(
399            ca_certs=util.ca_certs_path(),
400            cache=http_cache('discovery') if cache else None,
401            disable_ssl_certificate_validation=bool(insecure),
402        )
403    if http.timeout is None:
404        http.timeout = timeout
405    http = _patch_http_request(http, token, num_retries)
406
407    # The first time a client is instantiated, temporarily route
408    # googleapiclient.http retry logs if they're not already. These are
409    # important because temporary problems fetching the discovery document
410    # can cause clients to appear to hang early. This can be removed after
411    # we have a more general story for handling googleapiclient logs (#20521).
412    client_logger = logging.getLogger('googleapiclient.http')
413    # "first time a client is instantiated" = thread that acquires this lock
414    # It is only released explicitly by calling _reset_googleapiclient_logging.
415    # googleapiclient sets up its own NullHandler so we detect if logging is
416    # configured by looking for a real handler anywhere in the hierarchy.
417    client_logger_unconfigured = _googleapiclient_log_lock.acquire(blocking=False) and all(
418        isinstance(handler, logging.NullHandler)
419        for logger_name in ['', 'googleapiclient', 'googleapiclient.http']
420        for handler in logging.getLogger(logger_name).handlers
421    )
422    if client_logger_unconfigured:
423        client_level = client_logger.level
424        client_filter = GoogleHTTPClientFilter()
425        client_logger.addFilter(client_filter)
426        client_logger.addHandler(log_handler)
427        if logging.NOTSET < client_level < client_filter.retry_levelno:
428            client_logger.setLevel(client_level)
429        else:
430            client_logger.setLevel(client_filter.retry_levelno)
431    try:
432        svc = apiclient_discovery.build(
433            'arvados', version,
434            cache_discovery=False,
435            discoveryServiceUrl=discoveryServiceUrl,
436            http=http,
437            num_retries=num_retries,
438            **kwargs,
439        )
440    finally:
441        if client_logger_unconfigured:
442            client_logger.removeHandler(log_handler)
443            client_logger.removeFilter(client_filter)
444            client_logger.setLevel(client_level)
445    svc.api_token = token
446    svc.insecure = insecure
447    svc.request_id = request_id
448    svc.config = lambda: util.get_config_once(svc)
449    svc.vocabulary = lambda: util.get_vocabulary_once(svc)
450    svc.close_connections = types.MethodType(_close_connections, svc)
451    http.max_request_size = svc._rootDesc.get('maxRequestSize', 0)
452    http.cache = None
453    http._request_id = lambda: svc.request_id or util.new_request_id()
454    return svc
455
456def normalize_api_kwargs(
457        version: Optional[str]=None,
458        discoveryServiceUrl: Optional[str]=None,
459        host: Optional[str]=None,
460        token: Optional[str]=None,
461        **kwargs: Any,
462) -> Dict[str, Any]:
463    """Validate kwargs from `api` and build kwargs for `api_client`
464
465    This method takes high-level keyword arguments passed to the `api`
466    constructor and normalizes them into a new dictionary that can be passed
467    as keyword arguments to `api_client`. It raises `ValueError` if required
468    arguments are missing or conflict.
469
470    Arguments:
471
472    * version: str | None --- A string naming the version of the Arvados API
473      to use. If not specified, the code will log a warning and fall back to
474      'v1'.
475
476    * discoveryServiceUrl: str | None --- The URL used to discover APIs
477      passed directly to `googleapiclient.discovery.build`. It is an error
478      to pass both `discoveryServiceUrl` and `host`.
479
480    * host: str | None --- The hostname and optional port number of the
481      Arvados API server. Used to build `discoveryServiceUrl`. It is an
482      error to pass both `discoveryServiceUrl` and `host`.
483
484    * token: str --- The authentication token to send with each API call.
485
486    Additional keyword arguments will be included in the return value.
487    """
488    if discoveryServiceUrl and host:
489        raise ValueError("both discoveryServiceUrl and host provided")
490    elif discoveryServiceUrl:
491        url_src = "discoveryServiceUrl"
492    elif host:
493        url_src = "host argument"
494        discoveryServiceUrl = 'https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' % (host,)
495    elif token:
496        # This specific error message gets priority for backwards compatibility.
497        raise ValueError("token argument provided, but host missing.")
498    else:
499        raise ValueError("neither discoveryServiceUrl nor host provided")
500    if not token:
501        raise ValueError("%s provided, but token missing" % (url_src,))
502    if not version:
503        version = 'v1'
504        _logger.info(
505            "Using default API version. Call arvados.api(%r) instead.",
506            version,
507        )
508    return {
509        'discoveryServiceUrl': discoveryServiceUrl,
510        'token': token,
511        'version': version,
512        **kwargs,
513    }
514
515def api_kwargs_from_config(
516        version: Optional[str]=None,
517        apiconfig: Optional[Mapping[str, str]]=None,
518        **kwargs: Any
519) -> Dict[str, Any]:
520    """Build `api_client` keyword arguments from configuration
521
522    This function accepts a mapping with Arvados configuration settings like
523    `ARVADOS_API_HOST` and converts them into a mapping of keyword arguments
524    that can be passed to `api_client`. If `ARVADOS_API_HOST` or
525    `ARVADOS_API_TOKEN` are not configured, it raises `ValueError`.
526
527    Arguments:
528
529    * version: str | None --- A string naming the version of the Arvados API
530      to use. If not specified, the code will log a warning and fall back to
531      'v1'.
532
533    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
534      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
535      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
536      `arvados.config.settings` to get these parameters from user
537      configuration.
538
539    Additional keyword arguments will be included in the return value.
540    """
541    if apiconfig is None:
542        apiconfig = config.settings()
543    missing = " and ".join(
544        key
545        for key in ['ARVADOS_API_HOST', 'ARVADOS_API_TOKEN']
546        if key not in apiconfig
547    )
548    if missing:
549        raise ValueError(
550            "%s not set.\nPlease set in %s or export environment variable." %
551            (missing, config.default_config_file),
552        )
553    return normalize_api_kwargs(
554        version,
555        None,
556        apiconfig['ARVADOS_API_HOST'],
557        apiconfig['ARVADOS_API_TOKEN'],
558        insecure=config.flag_is_true('ARVADOS_API_HOST_INSECURE', apiconfig),
559        **kwargs,
560    )
561
562def api(
563        version: Optional[str]=None,
564        cache: bool=True,
565        host: Optional[str]=None,
566        token: Optional[str]=None,
567        insecure: bool=False,
568        request_id: Optional[str]=None,
569        timeout: int=5*60,
570        *,
571        discoveryServiceUrl: Optional[str]=None,
572        **kwargs: Any,
573) -> ThreadSafeAPIClient:
574    """Dynamically build an Arvados API client
575
576    This function provides a high-level "do what I mean" interface to build an
577    Arvados API client object. You can call it with no arguments to build a
578    client from user configuration; pass `host` and `token` arguments just
579    like you would write in user configuration; or pass additional arguments
580    for lower-level control over the client.
581
582    This function returns a `arvados.api.ThreadSafeAPIClient`, an
583    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
584    you're handling concurrency yourself and/or your application is very
585    performance-sensitive, consider calling `api_client` directly.
586
587    Arguments:
588
589    * version: str | None --- A string naming the version of the Arvados API
590      to use. If not specified, the code will log a warning and fall back to
591      'v1'.
592
593    * host: str | None --- The hostname and optional port number of the
594      Arvados API server.
595
596    * token: str | None --- The authentication token to send with each API
597      call.
598
599    * discoveryServiceUrl: str | None --- The URL used to discover APIs
600      passed directly to `googleapiclient.discovery.build`.
601
602    If `host`, `token`, and `discoveryServiceUrl` are all omitted, `host` and
603    `token` will be loaded from the user's configuration. Otherwise, you must
604    pass `token` and one of `host` or `discoveryServiceUrl`. It is an error to
605    pass both `host` and `discoveryServiceUrl`.
606
607    Other arguments are passed directly to `api_client`. See that function's
608    docstring for more information about their meaning.
609    """
610    kwargs.update(
611        cache=cache,
612        insecure=insecure,
613        request_id=request_id,
614        timeout=timeout,
615    )
616    if discoveryServiceUrl or host or token:
617        kwargs.update(normalize_api_kwargs(version, discoveryServiceUrl, host, token))
618    else:
619        kwargs.update(api_kwargs_from_config(version))
620    version = kwargs.pop('version')
621    return ThreadSafeAPIClient({}, {}, kwargs, version)
622
623def api_from_config(
624        version: Optional[str]=None,
625        apiconfig: Optional[Mapping[str, str]]=None,
626        **kwargs: Any
627) -> ThreadSafeAPIClient:
628    """Build an Arvados API client from a configuration mapping
629
630    This function builds an Arvados API client from a mapping with user
631    configuration. It accepts that mapping as an argument, so you can use a
632    configuration that's different from what the user has set up.
633
634    This function returns a `arvados.api.ThreadSafeAPIClient`, an
635    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
636    you're handling concurrency yourself and/or your application is very
637    performance-sensitive, consider calling `api_client` directly.
638
639    Arguments:
640
641    * version: str | None --- A string naming the version of the Arvados API
642      to use. If not specified, the code will log a warning and fall back to
643      'v1'.
644
645    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
646      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
647      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
648      `arvados.config.settings` to get these parameters from user
649      configuration.
650
651    Other arguments are passed directly to `api_client`. See that function's
652    docstring for more information about their meaning.
653    """
654    return api(**api_kwargs_from_config(version, apiconfig, **kwargs))
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.

class ThreadSafeHTTPCache:
192class ThreadSafeHTTPCache:
193    """Thread-safe replacement for `httplib2.FileCache`
194
195    `arvados.api.http_cache` is the preferred way to construct this object.
196    Refer to that function's docstring for details.
197    """
198
199    def __init__(self, path=None, max_age=None):
200        self._dir = path
201        if max_age is not None:
202            try:
203                self._clean(threshold=time.time() - max_age)
204            except:
205                pass
206
207    def _clean(self, threshold=0):
208        for ent in os.listdir(self._dir):
209            fnm = os.path.join(self._dir, ent)
210            if os.path.isdir(fnm) or not fnm.endswith('.tmp'):
211                continue
212            stat = os.lstat(fnm)
213            if stat.st_mtime < threshold:
214                try:
215                    os.unlink(fnm)
216                except OSError as err:
217                    if err.errno != errno.ENOENT:
218                        raise
219
220    def __str__(self):
221        return self._dir
222
223    def _filename(self, url):
224        return os.path.join(self._dir, hashlib.md5(url.encode('utf-8')).hexdigest()+'.tmp')
225
226    def get(self, url):
227        filename = self._filename(url)
228        try:
229            with open(filename, 'rb') as f:
230                return f.read()
231        except (IOError, OSError):
232            return None
233
234    def set(self, url, content):
235        try:
236            fd, tempname = tempfile.mkstemp(dir=self._dir)
237        except:
238            return None
239        try:
240            try:
241                f = os.fdopen(fd, 'wb')
242            except:
243                os.close(fd)
244                raise
245            try:
246                f.write(content)
247            finally:
248                f.close()
249            os.rename(tempname, self._filename(url))
250            tempname = None
251        finally:
252            if tempname:
253                os.unlink(tempname)
254
255    def delete(self, url):
256        try:
257            os.unlink(self._filename(url))
258        except OSError as err:
259            if err.errno != errno.ENOENT:
260                raise

Thread-safe replacement for httplib2.FileCache

arvados.api.http_cache is the preferred way to construct this object. Refer to that function’s docstring for details.

ThreadSafeHTTPCache(path=None, max_age=None)
199    def __init__(self, path=None, max_age=None):
200        self._dir = path
201        if max_age is not None:
202            try:
203                self._clean(threshold=time.time() - max_age)
204            except:
205                pass
def get(self, url):
226    def get(self, url):
227        filename = self._filename(url)
228        try:
229            with open(filename, 'rb') as f:
230                return f.read()
231        except (IOError, OSError):
232            return None
def set(self, url, content):
234    def set(self, url, content):
235        try:
236            fd, tempname = tempfile.mkstemp(dir=self._dir)
237        except:
238            return None
239        try:
240            try:
241                f = os.fdopen(fd, 'wb')
242            except:
243                os.close(fd)
244                raise
245            try:
246                f.write(content)
247            finally:
248                f.close()
249            os.rename(tempname, self._filename(url))
250            tempname = None
251        finally:
252            if tempname:
253                os.unlink(tempname)
def delete(self, url):
255    def delete(self, url):
256        try:
257            os.unlink(self._filename(url))
258        except OSError as err:
259            if err.errno != errno.ENOENT:
260                raise
class ThreadSafeAPIClient:
263class ThreadSafeAPIClient(object):
264    """Thread-safe wrapper for an Arvados API client
265
266    This class takes all the arguments necessary to build a lower-level
267    Arvados API client `googleapiclient.discovery.Resource`, then
268    transparently builds and wraps a unique object per thread. This works
269    around the fact that the client's underlying HTTP client object is not
270    thread-safe.
271
272    Arguments:
273
274    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
275      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
276      `ARVADOS_API_HOST_INSECURE`. If not provided, uses
277      `arvados.config.settings` to get these parameters from user
278      configuration.  You can pass an empty mapping to build the client
279      solely from `api_params`.
280
281    * keep_params: Mapping[str, Any] --- Keyword arguments used to construct
282      an associated `arvados.keep.KeepClient`.
283
284    * api_params: Mapping[str, Any] --- Keyword arguments used to construct
285      each thread's API client. These have the same meaning as in the
286      `arvados.api.api` function.
287
288    * version: str | None --- A string naming the version of the Arvados API
289      to use. If not specified, the code will log a warning and fall back to
290      `'v1'`.
291    """
292    def __init__(
293            self,
294            apiconfig: Optional[Mapping[str, str]]=None,
295            keep_params: Optional[Mapping[str, Any]]={},
296            api_params: Optional[Mapping[str, Any]]={},
297            version: Optional[str]=None,
298    ) -> None:
299        if apiconfig or apiconfig is None:
300            self._api_kwargs = api_kwargs_from_config(version, apiconfig, **api_params)
301        else:
302            self._api_kwargs = normalize_api_kwargs(version, **api_params)
303        self.api_token = self._api_kwargs['token']
304        self.request_id = self._api_kwargs.get('request_id')
305        self.local = threading.local()
306        self.keep = keep.KeepClient(api_client=self, **keep_params)
307
308    def localapi(self) -> 'googleapiclient.discovery.Resource':
309        try:
310            client = self.local.api
311        except AttributeError:
312            client = api_client(**self._api_kwargs)
313            client._http._request_id = lambda: self.request_id or util.new_request_id()
314            self.local.api = client
315        return client
316
317    def __getattr__(self, name: str) -> Any:
318        # Proxy nonexistent attributes to the thread-local API client.
319        return getattr(self.localapi(), name)

Thread-safe wrapper for an Arvados API client

This class takes all the arguments necessary to build a lower-level Arvados API client googleapiclient.discovery.Resource, then transparently builds and wraps a unique object per thread. This works around the fact that the client’s underlying HTTP client object is not thread-safe.

Arguments:

  • 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, uses arvados.config.settings to get these parameters from user configuration. You can pass an empty mapping to build the client solely from api_params.

  • keep_params: Mapping[str, Any] --- Keyword arguments used to construct an associated arvados.keep.KeepClient.

  • api_params: Mapping[str, Any] — Keyword arguments used to construct each thread’s API client. These have the same meaning as in the arvados.api.api function.

  • 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'.

ThreadSafeAPIClient( apiconfig: Optional[Mapping[str, str]] = None, keep_params: Optional[Mapping[str, Any]] = {}, api_params: Optional[Mapping[str, Any]] = {}, version: Optional[str] = None)
292    def __init__(
293            self,
294            apiconfig: Optional[Mapping[str, str]]=None,
295            keep_params: Optional[Mapping[str, Any]]={},
296            api_params: Optional[Mapping[str, Any]]={},
297            version: Optional[str]=None,
298    ) -> None:
299        if apiconfig or apiconfig is None:
300            self._api_kwargs = api_kwargs_from_config(version, apiconfig, **api_params)
301        else:
302            self._api_kwargs = normalize_api_kwargs(version, **api_params)
303        self.api_token = self._api_kwargs['token']
304        self.request_id = self._api_kwargs.get('request_id')
305        self.local = threading.local()
306        self.keep = keep.KeepClient(api_client=self, **keep_params)
api_token
request_id
local
keep
def localapi(self) -> googleapiclient.discovery.Resource:
308    def localapi(self) -> 'googleapiclient.discovery.Resource':
309        try:
310            client = self.local.api
311        except AttributeError:
312            client = api_client(**self._api_kwargs)
313            client._http._request_id = lambda: self.request_id or util.new_request_id()
314            self.local.api = client
315        return client
def http_cache(data_type: str) -> Optional[ThreadSafeHTTPCache]:
322def http_cache(data_type: str) -> Optional[ThreadSafeHTTPCache]:
323    """Set up an HTTP file cache
324
325    This function constructs and returns an `arvados.api.ThreadSafeHTTPCache`
326    backed by the filesystem under a cache directory from the environment, or
327    `None` if the directory cannot be set up. The return value can be passed to
328    `httplib2.Http` as the `cache` argument.
329
330    Arguments:
331
332    * data_type: str --- The name of the subdirectory
333      where data is cached.
334    """
335    try:
336        path = basedirs.BaseDirectories('CACHE').storage_path(data_type)
337    except (OSError, RuntimeError):
338        return None
339    else:
340        return ThreadSafeHTTPCache(str(path), max_age=60*60*24*2)

Set up an HTTP file cache

This function constructs and returns an arvados.api.ThreadSafeHTTPCache backed by the filesystem under a cache directory from the environment, 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 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:
342def api_client(
343        version: str,
344        discoveryServiceUrl: str,
345        token: str,
346        *,
347        cache: bool=True,
348        http: Optional[httplib2.Http]=None,
349        insecure: bool=False,
350        num_retries: int=10,
351        request_id: Optional[str]=None,
352        timeout: int=5*60,
353        **kwargs: Any,
354) -> apiclient_discovery.Resource:
355    """Build an Arvados API client
356
357    This function returns a `googleapiclient.discovery.Resource` object
358    constructed from the given arguments. This is a relatively low-level
359    interface that requires all the necessary inputs as arguments. Most
360    users will prefer to use `api` which can accept more flexible inputs.
361
362    Arguments:
363
364    * version: str --- A string naming the version of the Arvados API to use.
365
366    * discoveryServiceUrl: str --- The URL used to discover APIs passed
367      directly to `googleapiclient.discovery.build`.
368
369    * token: str --- The authentication token to send with each API call.
370
371    Keyword-only arguments:
372
373    * cache: bool --- If true, loads the API discovery document from, or
374      saves it to, a cache on disk.
375
376    * http: httplib2.Http | None --- The HTTP client object the API client
377      object will use to make requests.  If not provided, this function will
378      build its own to use. Either way, the object will be patched as part
379      of the build process.
380
381    * insecure: bool --- If true, ignore SSL certificate validation
382      errors. Default `False`.
383
384    * num_retries: int --- The number of times to retry each API request if
385      it encounters a temporary failure. Default 10.
386
387    * request_id: str | None --- Default `X-Request-Id` header value for
388      outgoing requests that don't already provide one. If `None` or
389      omitted, generate a random ID. When retrying failed requests, the same
390      ID is used on all attempts.
391
392    * timeout: int --- A timeout value for HTTP requests in seconds. Default
393      300 (5 minutes).
394
395    Additional keyword arguments will be passed directly to
396    `googleapiclient.discovery.build`.
397    """
398    if http is None:
399        http = httplib2.Http(
400            ca_certs=util.ca_certs_path(),
401            cache=http_cache('discovery') if cache else None,
402            disable_ssl_certificate_validation=bool(insecure),
403        )
404    if http.timeout is None:
405        http.timeout = timeout
406    http = _patch_http_request(http, token, num_retries)
407
408    # The first time a client is instantiated, temporarily route
409    # googleapiclient.http retry logs if they're not already. These are
410    # important because temporary problems fetching the discovery document
411    # can cause clients to appear to hang early. This can be removed after
412    # we have a more general story for handling googleapiclient logs (#20521).
413    client_logger = logging.getLogger('googleapiclient.http')
414    # "first time a client is instantiated" = thread that acquires this lock
415    # It is only released explicitly by calling _reset_googleapiclient_logging.
416    # googleapiclient sets up its own NullHandler so we detect if logging is
417    # configured by looking for a real handler anywhere in the hierarchy.
418    client_logger_unconfigured = _googleapiclient_log_lock.acquire(blocking=False) and all(
419        isinstance(handler, logging.NullHandler)
420        for logger_name in ['', 'googleapiclient', 'googleapiclient.http']
421        for handler in logging.getLogger(logger_name).handlers
422    )
423    if client_logger_unconfigured:
424        client_level = client_logger.level
425        client_filter = GoogleHTTPClientFilter()
426        client_logger.addFilter(client_filter)
427        client_logger.addHandler(log_handler)
428        if logging.NOTSET < client_level < client_filter.retry_levelno:
429            client_logger.setLevel(client_level)
430        else:
431            client_logger.setLevel(client_filter.retry_levelno)
432    try:
433        svc = apiclient_discovery.build(
434            'arvados', version,
435            cache_discovery=False,
436            discoveryServiceUrl=discoveryServiceUrl,
437            http=http,
438            num_retries=num_retries,
439            **kwargs,
440        )
441    finally:
442        if client_logger_unconfigured:
443            client_logger.removeHandler(log_handler)
444            client_logger.removeFilter(client_filter)
445            client_logger.setLevel(client_level)
446    svc.api_token = token
447    svc.insecure = insecure
448    svc.request_id = request_id
449    svc.config = lambda: util.get_config_once(svc)
450    svc.vocabulary = lambda: util.get_vocabulary_once(svc)
451    svc.close_connections = types.MethodType(_close_connections, svc)
452    http.max_request_size = svc._rootDesc.get('maxRequestSize', 0)
453    http.cache = None
454    http._request_id = lambda: svc.request_id or util.new_request_id()
455    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.

  • 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]:
457def normalize_api_kwargs(
458        version: Optional[str]=None,
459        discoveryServiceUrl: Optional[str]=None,
460        host: Optional[str]=None,
461        token: Optional[str]=None,
462        **kwargs: Any,
463) -> Dict[str, Any]:
464    """Validate kwargs from `api` and build kwargs for `api_client`
465
466    This method takes high-level keyword arguments passed to the `api`
467    constructor and normalizes them into a new dictionary that can be passed
468    as keyword arguments to `api_client`. It raises `ValueError` if required
469    arguments are missing or conflict.
470
471    Arguments:
472
473    * version: str | None --- A string naming the version of the Arvados API
474      to use. If not specified, the code will log a warning and fall back to
475      'v1'.
476
477    * discoveryServiceUrl: str | None --- The URL used to discover APIs
478      passed directly to `googleapiclient.discovery.build`. It is an error
479      to pass both `discoveryServiceUrl` and `host`.
480
481    * host: str | None --- The hostname and optional port number of the
482      Arvados API server. Used to build `discoveryServiceUrl`. It is an
483      error to pass both `discoveryServiceUrl` and `host`.
484
485    * token: str --- The authentication token to send with each API call.
486
487    Additional keyword arguments will be included in the return value.
488    """
489    if discoveryServiceUrl and host:
490        raise ValueError("both discoveryServiceUrl and host provided")
491    elif discoveryServiceUrl:
492        url_src = "discoveryServiceUrl"
493    elif host:
494        url_src = "host argument"
495        discoveryServiceUrl = 'https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' % (host,)
496    elif token:
497        # This specific error message gets priority for backwards compatibility.
498        raise ValueError("token argument provided, but host missing.")
499    else:
500        raise ValueError("neither discoveryServiceUrl nor host provided")
501    if not token:
502        raise ValueError("%s provided, but token missing" % (url_src,))
503    if not version:
504        version = 'v1'
505        _logger.info(
506            "Using default API version. Call arvados.api(%r) instead.",
507            version,
508        )
509    return {
510        'discoveryServiceUrl': discoveryServiceUrl,
511        'token': token,
512        'version': version,
513        **kwargs,
514    }

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]:
516def api_kwargs_from_config(
517        version: Optional[str]=None,
518        apiconfig: Optional[Mapping[str, str]]=None,
519        **kwargs: Any
520) -> Dict[str, Any]:
521    """Build `api_client` keyword arguments from configuration
522
523    This function accepts a mapping with Arvados configuration settings like
524    `ARVADOS_API_HOST` and converts them into a mapping of keyword arguments
525    that can be passed to `api_client`. If `ARVADOS_API_HOST` or
526    `ARVADOS_API_TOKEN` are not configured, it raises `ValueError`.
527
528    Arguments:
529
530    * version: str | None --- A string naming the version of the Arvados API
531      to use. If not specified, the code will log a warning and fall back to
532      'v1'.
533
534    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
535      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
536      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
537      `arvados.config.settings` to get these parameters from user
538      configuration.
539
540    Additional keyword arguments will be included in the return value.
541    """
542    if apiconfig is None:
543        apiconfig = config.settings()
544    missing = " and ".join(
545        key
546        for key in ['ARVADOS_API_HOST', 'ARVADOS_API_TOKEN']
547        if key not in apiconfig
548    )
549    if missing:
550        raise ValueError(
551            "%s not set.\nPlease set in %s or export environment variable." %
552            (missing, config.default_config_file),
553        )
554    return normalize_api_kwargs(
555        version,
556        None,
557        apiconfig['ARVADOS_API_HOST'],
558        apiconfig['ARVADOS_API_TOKEN'],
559        insecure=config.flag_is_true('ARVADOS_API_HOST_INSECURE', apiconfig),
560        **kwargs,
561    )

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) -> ThreadSafeAPIClient:
563def api(
564        version: Optional[str]=None,
565        cache: bool=True,
566        host: Optional[str]=None,
567        token: Optional[str]=None,
568        insecure: bool=False,
569        request_id: Optional[str]=None,
570        timeout: int=5*60,
571        *,
572        discoveryServiceUrl: Optional[str]=None,
573        **kwargs: Any,
574) -> ThreadSafeAPIClient:
575    """Dynamically build an Arvados API client
576
577    This function provides a high-level "do what I mean" interface to build an
578    Arvados API client object. You can call it with no arguments to build a
579    client from user configuration; pass `host` and `token` arguments just
580    like you would write in user configuration; or pass additional arguments
581    for lower-level control over the client.
582
583    This function returns a `arvados.api.ThreadSafeAPIClient`, an
584    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
585    you're handling concurrency yourself and/or your application is very
586    performance-sensitive, consider calling `api_client` directly.
587
588    Arguments:
589
590    * version: str | None --- A string naming the version of the Arvados API
591      to use. If not specified, the code will log a warning and fall back to
592      'v1'.
593
594    * host: str | None --- The hostname and optional port number of the
595      Arvados API server.
596
597    * token: str | None --- The authentication token to send with each API
598      call.
599
600    * discoveryServiceUrl: str | None --- The URL used to discover APIs
601      passed directly to `googleapiclient.discovery.build`.
602
603    If `host`, `token`, and `discoveryServiceUrl` are all omitted, `host` and
604    `token` will be loaded from the user's configuration. Otherwise, you must
605    pass `token` and one of `host` or `discoveryServiceUrl`. It is an error to
606    pass both `host` and `discoveryServiceUrl`.
607
608    Other arguments are passed directly to `api_client`. See that function's
609    docstring for more information about their meaning.
610    """
611    kwargs.update(
612        cache=cache,
613        insecure=insecure,
614        request_id=request_id,
615        timeout=timeout,
616    )
617    if discoveryServiceUrl or host or token:
618        kwargs.update(normalize_api_kwargs(version, discoveryServiceUrl, host, token))
619    else:
620        kwargs.update(api_kwargs_from_config(version))
621    version = kwargs.pop('version')
622    return ThreadSafeAPIClient({}, {}, 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.api.ThreadSafeAPIClient, 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) -> ThreadSafeAPIClient:
624def api_from_config(
625        version: Optional[str]=None,
626        apiconfig: Optional[Mapping[str, str]]=None,
627        **kwargs: Any
628) -> ThreadSafeAPIClient:
629    """Build an Arvados API client from a configuration mapping
630
631    This function builds an Arvados API client from a mapping with user
632    configuration. It accepts that mapping as an argument, so you can use a
633    configuration that's different from what the user has set up.
634
635    This function returns a `arvados.api.ThreadSafeAPIClient`, an
636    API-compatible wrapper around `googleapiclient.discovery.Resource`. If
637    you're handling concurrency yourself and/or your application is very
638    performance-sensitive, consider calling `api_client` directly.
639
640    Arguments:
641
642    * version: str | None --- A string naming the version of the Arvados API
643      to use. If not specified, the code will log a warning and fall back to
644      'v1'.
645
646    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
647      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
648      `ARVADOS_API_HOST_INSECURE`. If not provided, calls
649      `arvados.config.settings` to get these parameters from user
650      configuration.
651
652    Other arguments are passed directly to `api_client`. See that function's
653    docstring for more information about their meaning.
654    """
655    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.api.ThreadSafeAPIClient, 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.