arvados.safeapi

Thread-safe wrapper for an Arvados API client

This module provides ThreadSafeApiCache, a thread-safe, API-compatible Arvados API client.

 1# Copyright (C) The Arvados Authors. All rights reserved.
 2#
 3# SPDX-License-Identifier: Apache-2.0
 4"""Thread-safe wrapper for an Arvados API client
 5
 6This module provides `ThreadSafeApiCache`, a thread-safe, API-compatible
 7Arvados API client.
 8"""
 9
10import sys
11import threading
12
13from typing import (
14    Any,
15    Mapping,
16    Optional,
17)
18
19from . import config
20from . import keep
21from . import util
22
23api = sys.modules['arvados.api']
24
25class ThreadSafeApiCache(object):
26    """Thread-safe wrapper for an Arvados API client
27
28    This class takes all the arguments necessary to build a lower-level
29    Arvados API client `googleapiclient.discovery.Resource`, then
30    transparently builds and wraps a unique object per thread. This works
31    around the fact that the client's underlying HTTP client object is not
32    thread-safe.
33
34    Arguments:
35
36    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
37      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
38      `ARVADOS_API_HOST_INSECURE`. If not provided, uses
39      `arvados.config.settings` to get these parameters from user
40      configuration.  You can pass an empty mapping to build the client
41      solely from `api_params`.
42
43    * keep_params: Mapping[str, Any] --- Keyword arguments used to construct
44      an associated `arvados.keep.KeepClient`.
45
46    * api_params: Mapping[str, Any] --- Keyword arguments used to construct
47      each thread's API client. These have the same meaning as in the
48      `arvados.api.api` function.
49
50    * version: str | None --- A string naming the version of the Arvados API
51      to use. If not specified, the code will log a warning and fall back to
52      `'v1'`.
53    """
54    def __init__(
55            self,
56            apiconfig: Optional[Mapping[str, str]]=None,
57            keep_params: Optional[Mapping[str, Any]]={},
58            api_params: Optional[Mapping[str, Any]]={},
59            version: Optional[str]=None,
60    ) -> None:
61        if apiconfig or apiconfig is None:
62            self._api_kwargs = api.api_kwargs_from_config(version, apiconfig, **api_params)
63        else:
64            self._api_kwargs = api.normalize_api_kwargs(version, **api_params)
65        self.api_token = self._api_kwargs['token']
66        self.request_id = self._api_kwargs.get('request_id')
67        self.local = threading.local()
68        self.keep = keep.KeepClient(api_client=self, **keep_params)
69
70    def localapi(self) -> 'googleapiclient.discovery.Resource':
71        try:
72            client = self.local.api
73        except AttributeError:
74            client = api.api_client(**self._api_kwargs)
75            client._http._request_id = lambda: self.request_id or util.new_request_id()
76            self.local.api = client
77        return client
78
79    def __getattr__(self, name: str) -> Any:
80        # Proxy nonexistent attributes to the thread-local API client.
81        return getattr(self.localapi(), name)
class ThreadSafeApiCache:
26class ThreadSafeApiCache(object):
27    """Thread-safe wrapper for an Arvados API client
28
29    This class takes all the arguments necessary to build a lower-level
30    Arvados API client `googleapiclient.discovery.Resource`, then
31    transparently builds and wraps a unique object per thread. This works
32    around the fact that the client's underlying HTTP client object is not
33    thread-safe.
34
35    Arguments:
36
37    * apiconfig: Mapping[str, str] | None --- A mapping with entries for
38      `ARVADOS_API_HOST`, `ARVADOS_API_TOKEN`, and optionally
39      `ARVADOS_API_HOST_INSECURE`. If not provided, uses
40      `arvados.config.settings` to get these parameters from user
41      configuration.  You can pass an empty mapping to build the client
42      solely from `api_params`.
43
44    * keep_params: Mapping[str, Any] --- Keyword arguments used to construct
45      an associated `arvados.keep.KeepClient`.
46
47    * api_params: Mapping[str, Any] --- Keyword arguments used to construct
48      each thread's API client. These have the same meaning as in the
49      `arvados.api.api` function.
50
51    * version: str | None --- A string naming the version of the Arvados API
52      to use. If not specified, the code will log a warning and fall back to
53      `'v1'`.
54    """
55    def __init__(
56            self,
57            apiconfig: Optional[Mapping[str, str]]=None,
58            keep_params: Optional[Mapping[str, Any]]={},
59            api_params: Optional[Mapping[str, Any]]={},
60            version: Optional[str]=None,
61    ) -> None:
62        if apiconfig or apiconfig is None:
63            self._api_kwargs = api.api_kwargs_from_config(version, apiconfig, **api_params)
64        else:
65            self._api_kwargs = api.normalize_api_kwargs(version, **api_params)
66        self.api_token = self._api_kwargs['token']
67        self.request_id = self._api_kwargs.get('request_id')
68        self.local = threading.local()
69        self.keep = keep.KeepClient(api_client=self, **keep_params)
70
71    def localapi(self) -> 'googleapiclient.discovery.Resource':
72        try:
73            client = self.local.api
74        except AttributeError:
75            client = api.api_client(**self._api_kwargs)
76            client._http._request_id = lambda: self.request_id or util.new_request_id()
77            self.local.api = client
78        return client
79
80    def __getattr__(self, name: str) -> Any:
81        # Proxy nonexistent attributes to the thread-local API client.
82        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'.

ThreadSafeApiCache( apiconfig: Optional[Mapping[str, str]] = None, keep_params: Optional[Mapping[str, Any]] = {}, api_params: Optional[Mapping[str, Any]] = {}, version: Optional[str] = None)
55    def __init__(
56            self,
57            apiconfig: Optional[Mapping[str, str]]=None,
58            keep_params: Optional[Mapping[str, Any]]={},
59            api_params: Optional[Mapping[str, Any]]={},
60            version: Optional[str]=None,
61    ) -> None:
62        if apiconfig or apiconfig is None:
63            self._api_kwargs = api.api_kwargs_from_config(version, apiconfig, **api_params)
64        else:
65            self._api_kwargs = api.normalize_api_kwargs(version, **api_params)
66        self.api_token = self._api_kwargs['token']
67        self.request_id = self._api_kwargs.get('request_id')
68        self.local = threading.local()
69        self.keep = keep.KeepClient(api_client=self, **keep_params)
api_token
request_id
local
keep
def localapi(self) -> googleapiclient.discovery.Resource:
71    def localapi(self) -> 'googleapiclient.discovery.Resource':
72        try:
73            client = self.local.api
74        except AttributeError:
75            client = api.api_client(**self._api_kwargs)
76            client._http._request_id = lambda: self.request_id or util.new_request_id()
77            self.local.api = client
78        return client