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:

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

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

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

Thread-safe replacement for httplib2.FileCache

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

ThreadSafeHTTPCache(path=None, max_age=None)
188    def __init__(self, path=None, max_age=None):
189        self._dir = path
190        if max_age is not None:
191            try:
192                self._clean(threshold=time.time() - max_age)
193            except:
194                pass
def get(self, url):
215    def get(self, url):
216        filename = self._filename(url)
217        try:
218            with open(filename, 'rb') as f:
219                return f.read()
220        except (IOError, OSError):
221            return None
def set(self, url, content):
223    def set(self, url, content):
224        try:
225            fd, tempname = tempfile.mkstemp(dir=self._dir)
226        except:
227            return None
228        try:
229            try:
230                f = os.fdopen(fd, 'wb')
231            except:
232                os.close(fd)
233                raise
234            try:
235                f.write(content)
236            finally:
237                f.close()
238            os.rename(tempname, self._filename(url))
239            tempname = None
240        finally:
241            if tempname:
242                os.unlink(tempname)
def delete(self, url):
244    def delete(self, url):
245        try:
246            os.unlink(self._filename(url))
247        except OSError as err:
248            if err.errno != errno.ENOENT:
249                raise
class ThreadSafeAPIClient:
252class ThreadSafeAPIClient(object):
253    """Thread-safe wrapper for an Arvados API client
254
255    This class takes all the arguments necessary to build a lower-level
256    Arvados API client `googleapiclient.discovery.Resource`, then
257    transparently builds and wraps a unique object per thread. This works
258    around the fact that the client's underlying HTTP client object is not
259    thread-safe.
260
261    Arguments:
262
263    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
264      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
265      `ARVADOS_API_HOST_INSECURE`. If not provided, uses
266      `arvados.config.settings` to get these parameters from user
267      configuration.  You can pass an empty mapping to build the client
268      solely from `api_params`.
269
270    * keep_params: Mapping[str, Any] --- Keyword arguments used to construct
271      an associated `arvados.keep.KeepClient`.
272
273    * api_params: Mapping[str, Any] --- Keyword arguments used to construct
274      each thread's API client. These have the same meaning as in the
275      `arvados.api.api` function.
276
277    * version: str | None --- A string naming the version of the Arvados API
278      to use. If not specified, the code will log a warning and fall back to
279      `'v1'`.
280    """
281    def __init__(
282            self,
283            apiconfig: Optional[Mapping[str, str]]=None,
284            keep_params: Optional[Mapping[str, Any]]={},
285            api_params: Optional[Mapping[str, Any]]={},
286            version: Optional[str]=None,
287    ) -> None:
288        if apiconfig or apiconfig is None:
289            self._api_kwargs = api_kwargs_from_config(version, apiconfig, **api_params)
290        else:
291            self._api_kwargs = normalize_api_kwargs(version, **api_params)
292        self.api_token = self._api_kwargs['token']
293        self.request_id = self._api_kwargs.get('request_id')
294        self.local = threading.local()
295        self.keep = keep.KeepClient(api_client=self, **keep_params)
296
297    def localapi(self) -> 'googleapiclient.discovery.Resource':
298        try:
299            client = self.local.api
300        except AttributeError:
301            client = api_client(**self._api_kwargs)
302            client._http._request_id = lambda: self.request_id or util.new_request_id()
303            self.local.api = client
304        return client
305
306    def __getattr__(self, name: str) -> Any:
307        # Proxy nonexistent attributes to the thread-local API client.
308        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 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)
281    def __init__(
282            self,
283            apiconfig: Optional[Mapping[str, str]]=None,
284            keep_params: Optional[Mapping[str, Any]]={},
285            api_params: Optional[Mapping[str, Any]]={},
286            version: Optional[str]=None,
287    ) -> None:
288        if apiconfig or apiconfig is None:
289            self._api_kwargs = api_kwargs_from_config(version, apiconfig, **api_params)
290        else:
291            self._api_kwargs = normalize_api_kwargs(version, **api_params)
292        self.api_token = self._api_kwargs['token']
293        self.request_id = self._api_kwargs.get('request_id')
294        self.local = threading.local()
295        self.keep = keep.KeepClient(api_client=self, **keep_params)
api_token
request_id
local
keep
def localapi(self) -> googleapiclient.discovery.Resource:
297    def localapi(self) -> 'googleapiclient.discovery.Resource':
298        try:
299            client = self.local.api
300        except AttributeError:
301            client = api_client(**self._api_kwargs)
302            client._http._request_id = lambda: self.request_id or util.new_request_id()
303            self.local.api = client
304        return client
def http_cache(data_type: str) -> Optional[ThreadSafeHTTPCache]:
311def http_cache(data_type: str) -> Optional[ThreadSafeHTTPCache]:
312    """Set up an HTTP file cache
313
314    This function constructs and returns an `arvados.api.ThreadSafeHTTPCache`
315    backed by the filesystem under a cache directory from the environment, or
316    `None` if the directory cannot be set up. The return value can be passed to
317    `httplib2.Http` as the `cache` argument.
318
319    Arguments:
320
321    * data_type: str --- The name of the subdirectory
322      where data is cached.
323    """
324    try:
325        path = basedirs.BaseDirectories('CACHE').storage_path(data_type)
326    except (OSError, RuntimeError):
327        return None
328    else:
329        return ThreadSafeHTTPCache(str(path), max_age=60*60*24*2)

Set up an HTTP file cache

This function constructs and returns an 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:
331def api_client(
332        version: str,
333        discoveryServiceUrl: str,
334        token: str,
335        *,
336        cache: bool=True,
337        http: Optional[httplib2.Http]=None,
338        insecure: bool=False,
339        num_retries: int=10,
340        request_id: Optional[str]=None,
341        timeout: int=5*60,
342        **kwargs: Any,
343) -> apiclient_discovery.Resource:
344    """Build an Arvados API client
345
346    This function returns a `googleapiclient.discovery.Resource` object
347    constructed from the given arguments. This is a relatively low-level
348    interface that requires all the necessary inputs as arguments. Most
349    users will prefer to use `api` which can accept more flexible inputs.
350
351    Arguments:
352
353    * version: str --- A string naming the version of the Arvados API to use.
354
355    * discoveryServiceUrl: str --- The URL used to discover APIs passed
356      directly to `googleapiclient.discovery.build`.
357
358    * token: str --- The authentication token to send with each API call.
359
360    Keyword-only arguments:
361
362    * cache: bool --- If true, loads the API discovery document from, or
363      saves it to, a cache on disk.
364
365    * http: httplib2.Http | None --- The HTTP client object the API client
366      object will use to make requests.  If not provided, this function will
367      build its own to use. Either way, the object will be patched as part
368      of the build process.
369
370    * insecure: bool --- If true, ignore SSL certificate validation
371      errors. Default `False`.
372
373    * num_retries: int --- The number of times to retry each API request if
374      it encounters a temporary failure. Default 10.
375
376    * request_id: str | None --- Default `X-Request-Id` header value for
377      outgoing requests that don't already provide one. If `None` or
378      omitted, generate a random ID. When retrying failed requests, the same
379      ID is used on all attempts.
380
381    * timeout: int --- A timeout value for HTTP requests in seconds. Default
382      300 (5 minutes).
383
384    Additional keyword arguments will be passed directly to
385    `googleapiclient.discovery.build`.
386    """
387    if http is None:
388        http = httplib2.Http(
389            ca_certs=util.ca_certs_path(),
390            cache=http_cache('discovery') if cache else None,
391            disable_ssl_certificate_validation=bool(insecure),
392        )
393    if http.timeout is None:
394        http.timeout = timeout
395    http = _patch_http_request(http, token, num_retries)
396
397    # The first time a client is instantiated, temporarily route
398    # googleapiclient.http retry logs if they're not already. These are
399    # important because temporary problems fetching the discovery document
400    # can cause clients to appear to hang early. This can be removed after
401    # we have a more general story for handling googleapiclient logs (#20521).
402    client_logger = logging.getLogger('googleapiclient.http')
403    # "first time a client is instantiated" = thread that acquires this lock
404    # It is never released.
405    # googleapiclient sets up its own NullHandler so we detect if logging is
406    # configured by looking for a real handler anywhere in the hierarchy.
407    client_logger_unconfigured = _googleapiclient_log_lock.acquire(blocking=False) and all(
408        isinstance(handler, logging.NullHandler)
409        for logger_name in ['', 'googleapiclient', 'googleapiclient.http']
410        for handler in logging.getLogger(logger_name).handlers
411    )
412    if client_logger_unconfigured:
413        client_level = client_logger.level
414        client_filter = GoogleHTTPClientFilter()
415        client_logger.addFilter(client_filter)
416        client_logger.addHandler(log_handler)
417        if logging.NOTSET < client_level < client_filter.retry_levelno:
418            client_logger.setLevel(client_level)
419        else:
420            client_logger.setLevel(client_filter.retry_levelno)
421    try:
422        svc = apiclient_discovery.build(
423            'arvados', version,
424            cache_discovery=False,
425            discoveryServiceUrl=discoveryServiceUrl,
426            http=http,
427            num_retries=num_retries,
428            **kwargs,
429        )
430    finally:
431        if client_logger_unconfigured:
432            client_logger.removeHandler(log_handler)
433            client_logger.removeFilter(client_filter)
434            client_logger.setLevel(client_level)
435    svc.api_token = token
436    svc.insecure = insecure
437    svc.request_id = request_id
438    svc.config = lambda: util.get_config_once(svc)
439    svc.vocabulary = lambda: util.get_vocabulary_once(svc)
440    svc.close_connections = types.MethodType(_close_connections, svc)
441    http.max_request_size = svc._rootDesc.get('maxRequestSize', 0)
442    http.cache = None
443    http._request_id = lambda: svc.request_id or util.new_request_id()
444    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]:
446def normalize_api_kwargs(
447        version: Optional[str]=None,
448        discoveryServiceUrl: Optional[str]=None,
449        host: Optional[str]=None,
450        token: Optional[str]=None,
451        **kwargs: Any,
452) -> Dict[str, Any]:
453    """Validate kwargs from `api` and build kwargs for `api_client`
454
455    This method takes high-level keyword arguments passed to the `api`
456    constructor and normalizes them into a new dictionary that can be passed
457    as keyword arguments to `api_client`. It raises `ValueError` if required
458    arguments are missing or conflict.
459
460    Arguments:
461
462    * version: str | None --- A string naming the version of the Arvados API
463      to use. If not specified, the code will log a warning and fall back to
464      'v1'.
465
466    * discoveryServiceUrl: str | None --- The URL used to discover APIs
467      passed directly to `googleapiclient.discovery.build`. It is an error
468      to pass both `discoveryServiceUrl` and `host`.
469
470    * host: str | None --- The hostname and optional port number of the
471      Arvados API server. Used to build `discoveryServiceUrl`. It is an
472      error to pass both `discoveryServiceUrl` and `host`.
473
474    * token: str --- The authentication token to send with each API call.
475
476    Additional keyword arguments will be included in the return value.
477    """
478    if discoveryServiceUrl and host:
479        raise ValueError("both discoveryServiceUrl and host provided")
480    elif discoveryServiceUrl:
481        url_src = "discoveryServiceUrl"
482    elif host:
483        url_src = "host argument"
484        discoveryServiceUrl = 'https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' % (host,)
485    elif token:
486        # This specific error message gets priority for backwards compatibility.
487        raise ValueError("token argument provided, but host missing.")
488    else:
489        raise ValueError("neither discoveryServiceUrl nor host provided")
490    if not token:
491        raise ValueError("%s provided, but token missing" % (url_src,))
492    if not version:
493        version = 'v1'
494        _logger.info(
495            "Using default API version. Call arvados.api(%r) instead.",
496            version,
497        )
498    return {
499        'discoveryServiceUrl': discoveryServiceUrl,
500        'token': token,
501        'version': version,
502        **kwargs,
503    }

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

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