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