elapi 1.0.7.dev4__tar.gz → 1.0.7.dev6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/PKG-INFO +34 -5
  2. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/README.md +32 -2
  3. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/pyproject.toml +11 -2
  4. elapi-1.0.7.dev6/src/elapi/VERSION +1 -0
  5. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/_names.py +3 -0
  6. elapi-1.0.7.dev6/src/elapi/api/__init__.py +15 -0
  7. {elapi-1.0.7.dev4/src/elapi → elapi-1.0.7.dev6/src/elapi/api}/api.py +130 -53
  8. elapi-1.0.7.dev4/src/elapi/validators/permission.py → elapi-1.0.7.dev6/src/elapi/api/validators.py +103 -6
  9. elapi-1.0.7.dev6/src/elapi/cli/__main__.py +4 -0
  10. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/cli/_plugin_handler.py +4 -1
  11. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/cli/doc.py +19 -1
  12. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/cli/elapi.py +406 -100
  13. elapi-1.0.7.dev6/src/elapi/configuration/__init__.py +43 -0
  14. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/configuration/_config_history.py +49 -3
  15. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/configuration/config.py +63 -81
  16. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/configuration/log_file.py +1 -1
  17. elapi-1.0.7.dev6/src/elapi/configuration/overridable_vars.py +45 -0
  18. elapi-1.0.7.dev6/src/elapi/configuration/validators.py +556 -0
  19. {elapi-1.0.7.dev4/src/elapi/validators → elapi-1.0.7.dev6/src/elapi/core_validators}/__init__.py +0 -2
  20. elapi-1.0.7.dev6/src/elapi/plugins/__init__.py +0 -0
  21. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/bill_teams/bill_teams.py +4 -4
  22. elapi-1.0.7.dev6/src/elapi/plugins/bill_teams/cli.py +509 -0
  23. elapi-1.0.7.dev6/src/elapi/plugins/bill_teams/generate_table.py +13 -0
  24. elapi-1.0.7.dev6/src/elapi/plugins/bill_teams/specification.py +47 -0
  25. elapi-1.0.7.dev4/src/elapi/plugins/bill_teams/validator.py → elapi-1.0.7.dev6/src/elapi/plugins/bill_teams/validators.py +168 -32
  26. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/commons/__init__.py +1 -0
  27. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/commons/cli_helpers.py +36 -3
  28. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/commons/export.py +5 -4
  29. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/commons/get_information.py +2 -2
  30. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/experiments/cli.py +19 -21
  31. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/experiments/experiments.py +2 -2
  32. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/show_config.py +87 -13
  33. elapi-1.0.7.dev6/src/elapi/validators.py +24 -0
  34. elapi-1.0.7.dev4/src/elapi/VERSION +0 -1
  35. elapi-1.0.7.dev4/src/elapi/cli/__init__.py +0 -1
  36. elapi-1.0.7.dev4/src/elapi/cli/__main__.py +0 -3
  37. elapi-1.0.7.dev4/src/elapi/configuration/__init__.py +0 -23
  38. elapi-1.0.7.dev4/src/elapi/plugins/bill_teams/cli.py +0 -386
  39. elapi-1.0.7.dev4/src/elapi/validators/identity.py +0 -163
  40. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/LICENSE +0 -0
  41. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/__init__.py +0 -0
  42. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/_exceptions.py +0 -0
  43. {elapi-1.0.7.dev4/src/elapi → elapi-1.0.7.dev6/src/elapi/api}/endpoint.py +0 -0
  44. {elapi-1.0.7.dev4/src/elapi/plugins → elapi-1.0.7.dev6/src/elapi/cli}/__init__.py +0 -0
  45. {elapi-1.0.7.dev4/src/elapi/validators → elapi-1.0.7.dev6/src/elapi/core_validators}/base.py +0 -0
  46. {elapi-1.0.7.dev4/src/elapi/validators → elapi-1.0.7.dev6/src/elapi/core_validators}/path.py +0 -0
  47. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/loggers/__init__.py +0 -0
  48. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/loggers/base.py +0 -0
  49. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/loggers/handlers/__init__.py +0 -0
  50. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/loggers/handlers/file.py +0 -0
  51. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/loggers/handlers/stderr.py +0 -0
  52. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/path.py +0 -0
  53. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/bill_teams/__init__.py +0 -0
  54. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/bill_teams/_doc.py +0 -0
  55. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/bill_teams/format.py +0 -0
  56. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/bill_teams/invoice.py +0 -0
  57. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/commons/get_location_from_headers.py +0 -0
  58. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/experiments/__init__.py +0 -0
  59. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/experiments/_doc.py +0 -0
  60. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/experiments/formats.py +0 -0
  61. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/plugins/version.py +0 -0
  62. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/styles/__init__.py +0 -0
  63. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/styles/_markdown_doc.py +0 -0
  64. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/styles/_missing.py +0 -0
  65. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/styles/base.py +0 -0
  66. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/styles/colors.py +0 -0
  67. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/styles/format.py +0 -0
  68. {elapi-1.0.7.dev4 → elapi-1.0.7.dev6}/src/elapi/styles/highlight.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: elapi
3
- Version: 1.0.7.dev4
3
+ Version: 1.0.7.dev6
4
4
  Summary: elAPI is a powerful, extensible API client for eLabFTW.
5
5
  Home-page: https://www.urz.uni-heidelberg.de/en/service-catalogue/software-and-applications/elabftw
6
6
  License: AGPL-3.0-only
@@ -27,12 +27,11 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
27
  Classifier: Topic :: System :: Systems Administration
28
28
  Classifier: Topic :: Utilities
29
29
  Requires-Dist: dynaconf (>=3.2.0,<4.0.0)
30
+ Requires-Dist: h2 (>=4.1.0,<5.0.0)
30
31
  Requires-Dist: httpx (>=0.24.1,<0.25.0)
31
32
  Requires-Dist: httpx-auth (>=0.17.0,<0.18.0)
32
33
  Requires-Dist: mistune (>=3.0.2,<4.0.0)
33
- Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
34
34
  Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
35
- Requires-Dist: tenacity (>=8.2.3,<9.0.0)
36
35
  Requires-Dist: typer[all] (>=0.9.0,<0.10.0)
37
36
  Description-Content-Type: text/markdown
38
37
 
@@ -111,7 +110,7 @@ configuration file present in the currently active directory has the highest pri
111
110
  directory has the lowest.
112
111
 
113
112
  The following parameters are currently configurable, with `host` and `api_token` being the required fields. For testing
114
- purposes it would be safe to store everything in `$HOME/.config/elapi.yml`.
113
+ purposes, it would be safe to store everything in `$HOME/.config/elapi.yml`.
115
114
 
116
115
  ```yaml
117
116
  # elAPI configuration
@@ -124,12 +123,42 @@ api_token: <token with at least read-access>
124
123
  # You can generate an API token from eLabFTW user panel -> API keys tab.
125
124
  export_dir: ~/Downloads/elAPI
126
125
  unsafe_api_token_warning: yes
126
+ enable_http2: no
127
127
  ```
128
128
 
129
- You can get an overview of detected configurations.
129
+ `export_dir` is where elAPI will export response content to if no path is provided to `--export/-E`.
130
+ When `unsafe_api_token_warning` is `True`, elAPI will show a warning if you're storing `elapi.yml` in the current
131
+ working directory, as it typically happens that developers accidentally commit and push configuration files with
132
+ secrets. `enable_http2` enables HTTP/2 protocol support which by default is turned off. Be aware
133
+ of [known issues](https://github.com/encode/httpx/discussions/2112) with
134
+ HTTP/2 if you are making async requests with heavy load.
135
+
136
+ You can get an overview of detected configuration with `elapi show-config`. `show-config` makes it easier to verify
137
+ which configuration values are actually used by elAPI, if you are working with multiple configuration files.
130
138
 
131
139
  ```shell
132
140
  $ elapi show-config
141
+
142
+
143
+ elapi configuration information
144
+
145
+ The following debug information includes configuration values and their sources as detected by elapi.
146
+
147
+ ▌ Name [Key]: Value ← Source
148
+
149
+ • Log file path: /Users/<username>/.local/share/elapi/elapi.log
150
+ • Host address [host]: https://demo.elabftw.net/api/v2 ← USER LEVEL
151
+ • API Token [api_token]: 00-55******55555 ← USER LEVEL
152
+ • Export directory [export_dir]: /Users/<username>/Downloads/elapi ← USER LEVEL
153
+ • App data directory: /Users/<username>/.local/share/elapi
154
+ • Caching directory: /private/var/tmp/elapi
155
+ • Unsafe API token use warning [unsafe_api_token_warning]: Yes ← USER LEVEL
156
+ • Enable HTTP/2 [enable_http2]: Yes ← USER LEVEL
157
+
158
+ Detected configuration sources that are in use:
159
+
160
+ • USER LEVEL: /Users/<username>/.config/elapi.yml
161
+
133
162
  ```
134
163
 
135
164
  If both `host` and `api_token` are detected, you are good to go!
@@ -73,7 +73,7 @@ configuration file present in the currently active directory has the highest pri
73
73
  directory has the lowest.
74
74
 
75
75
  The following parameters are currently configurable, with `host` and `api_token` being the required fields. For testing
76
- purposes it would be safe to store everything in `$HOME/.config/elapi.yml`.
76
+ purposes, it would be safe to store everything in `$HOME/.config/elapi.yml`.
77
77
 
78
78
  ```yaml
79
79
  # elAPI configuration
@@ -86,12 +86,42 @@ api_token: <token with at least read-access>
86
86
  # You can generate an API token from eLabFTW user panel -> API keys tab.
87
87
  export_dir: ~/Downloads/elAPI
88
88
  unsafe_api_token_warning: yes
89
+ enable_http2: no
89
90
  ```
90
91
 
91
- You can get an overview of detected configurations.
92
+ `export_dir` is where elAPI will export response content to if no path is provided to `--export/-E`.
93
+ When `unsafe_api_token_warning` is `True`, elAPI will show a warning if you're storing `elapi.yml` in the current
94
+ working directory, as it typically happens that developers accidentally commit and push configuration files with
95
+ secrets. `enable_http2` enables HTTP/2 protocol support which by default is turned off. Be aware
96
+ of [known issues](https://github.com/encode/httpx/discussions/2112) with
97
+ HTTP/2 if you are making async requests with heavy load.
98
+
99
+ You can get an overview of detected configuration with `elapi show-config`. `show-config` makes it easier to verify
100
+ which configuration values are actually used by elAPI, if you are working with multiple configuration files.
92
101
 
93
102
  ```shell
94
103
  $ elapi show-config
104
+
105
+
106
+ elapi configuration information
107
+
108
+ The following debug information includes configuration values and their sources as detected by elapi.
109
+
110
+ ▌ Name [Key]: Value ← Source
111
+
112
+ • Log file path: /Users/<username>/.local/share/elapi/elapi.log
113
+ • Host address [host]: https://demo.elabftw.net/api/v2 ← USER LEVEL
114
+ • API Token [api_token]: 00-55******55555 ← USER LEVEL
115
+ • Export directory [export_dir]: /Users/<username>/Downloads/elapi ← USER LEVEL
116
+ • App data directory: /Users/<username>/.local/share/elapi
117
+ • Caching directory: /private/var/tmp/elapi
118
+ • Unsafe API token use warning [unsafe_api_token_warning]: Yes ← USER LEVEL
119
+ • Enable HTTP/2 [enable_http2]: Yes ← USER LEVEL
120
+
121
+ Detected configuration sources that are in use:
122
+
123
+ • USER LEVEL: /Users/<username>/.config/elapi.yml
124
+
95
125
  ```
96
126
 
97
127
  If both `host` and `api_token` are detected, you are good to go!
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "elapi"
3
- version = "1.0.7.dev4"
3
+ version = "1.0.7.dev6"
4
4
  description = "elAPI is a powerful, extensible API client for eLabFTW."
5
5
  authors = [
6
6
  "Alexander Haller, Mahadi Xion <elabftw@uni-heidelberg.de>",
@@ -36,9 +36,18 @@ httpx = "^0.24.1"
36
36
  httpx-auth = "^0.17.0"
37
37
  typer = { extras = ["all"], version = "^0.9.0" }
38
38
  pyyaml = "^6.0.1"
39
+ mistune = "^3.0.2"
40
+ h2 = "^4.1.0"
41
+
42
+ [project.optional-dependencies]
43
+ uhd_urz = ["python-dateutil=^2.8.2", "tenacity=^8.2.3"]
44
+
45
+ [tool.poetry.group.uhd_urz]
46
+ optional = true
47
+
48
+ [tool.poetry.group.uhd_urz.dependencies]
39
49
  python-dateutil = "^2.8.2"
40
50
  tenacity = "^8.2.3"
41
- mistune = "^3.0.2"
42
51
 
43
52
  [tool.poetry.group.dev.dependencies]
44
53
  ptpython = "^3.0.23"
@@ -0,0 +1 @@
1
+ 1.0.7.dev6
@@ -38,6 +38,9 @@ KEY_HOST: str = "HOST"
38
38
  KEY_API_TOKEN: str = "API_TOKEN"
39
39
  KEY_EXPORT_DIR: str = "EXPORT_DIR"
40
40
  KEY_UNSAFE_TOKEN_WARNING: str = "UNSAFE_API_TOKEN_WARNING"
41
+ KEY_ENABLE_HTTP2: str = "ENABLE_HTTP2"
42
+ KEY_VERIFY_SSL: str = "VERIFY_SSL"
43
+ KEY_TIMEOUT: str = "TIMEOUT"
41
44
 
42
45
  # Log data directory with root permission
43
46
  LOG_DIR_ROOT: Path = Path(f"/var/log/{APP_NAME}")
@@ -0,0 +1,15 @@
1
+ # ruff: noqa: F401
2
+ from .api import (
3
+ APIRequest,
4
+ ElabFTWURLError,
5
+ ElabFTWURL,
6
+ GETRequest,
7
+ AsyncGETRequest,
8
+ POSTRequest,
9
+ AsyncPOSTRequest,
10
+ PATCHRequest,
11
+ AsyncPATCHRequest,
12
+ DELETERequest,
13
+ AsyncDELETERequest,
14
+ )
15
+ from .endpoint import FixedEndpoint, FixedAsyncEndpoint
@@ -5,21 +5,33 @@ from typing import Union, Optional, Tuple
5
5
  from httpx import Response, Client, AsyncClient, Limits
6
6
  from httpx_auth import HeaderApiKey
7
7
 
8
- from .configuration import API_TOKEN, TOKEN_BEARER, HOST
8
+ from ..configuration import (
9
+ TOKEN_BEARER,
10
+ get_active_host,
11
+ get_active_api_token,
12
+ get_active_enable_http2,
13
+ get_active_verify_ssl,
14
+ get_active_timeout,
15
+ )
9
16
 
10
17
 
11
18
  class APIRequest(ABC):
12
- __slots__ = "keep_session_open", "_client"
13
- host: str = HOST
14
- api_token: str = API_TOKEN
15
- header_name: str = TOKEN_BEARER
19
+ __slots__ = "keep_session_open", "_client", "host", "api_token", "header_name"
16
20
 
17
21
  def __init__(self, keep_session_open: bool = False, **kwargs):
22
+ self.host: str = get_active_host()
23
+ self.api_token: str = get_active_api_token().token
24
+ self.header_name: str = TOKEN_BEARER
18
25
  self.keep_session_open = keep_session_open
26
+ enable_http2 = get_active_enable_http2()
27
+ verify_ssl = get_active_verify_ssl()
28
+ timeout = get_active_timeout()
19
29
  _client = Client if not self.is_async_client else AsyncClient
20
30
  self._client: Union[Client, AsyncClient] = _client(
21
31
  auth=HeaderApiKey(api_key=self.api_token, header_name=self.header_name),
22
- verify=True,
32
+ http2=enable_http2,
33
+ verify=verify_ssl,
34
+ timeout=timeout,
23
35
  **kwargs,
24
36
  )
25
37
 
@@ -94,13 +106,15 @@ class ElabFTWURL:
94
106
  "experiments_status",
95
107
  "items_status",
96
108
  "teamgroups",
109
+ "tags",
97
110
  ),
98
111
  "todolist": (),
99
112
  "unfinished_steps": (),
100
113
  "users": ("notifications",),
101
114
  "idps": (),
115
+ "import": (),
116
+ "exports": (),
102
117
  }
103
- host = APIRequest.host
104
118
 
105
119
  def __init__(
106
120
  self,
@@ -116,6 +130,18 @@ class ElabFTWURL:
116
130
  self.sub_endpoint_id = sub_endpoint_id
117
131
  self.query = query
118
132
 
133
+ @property
134
+ def host(self) -> str:
135
+ return get_active_host()
136
+
137
+ @host.setter
138
+ def host(self, value):
139
+ raise AttributeError("Host value cannot be modified!")
140
+
141
+ @host.deleter
142
+ def host(self):
143
+ raise AttributeError("Host cannot be deleted!")
144
+
119
145
  @property
120
146
  def endpoint_name(self) -> str:
121
147
  return self._endpoint_name
@@ -138,10 +164,19 @@ class ElabFTWURL:
138
164
  @sub_endpoint_name.setter
139
165
  def sub_endpoint_name(self, value: str):
140
166
  if value is not None:
141
- if value.lower() not in ElabFTWURL.VALID_ENDPOINTS[self.endpoint_name]:
167
+ if value.lower() not in (
168
+ valid_sub_endpoint_name := ElabFTWURL.VALID_ENDPOINTS[
169
+ self.endpoint_name
170
+ ]
171
+ ):
172
+ if not valid_sub_endpoint_name:
173
+ raise ElabFTWURLError(
174
+ f"Endpoint '{self._endpoint_name}' does not have any sub-endpoint!"
175
+ )
176
+
142
177
  raise ElabFTWURLError(
143
178
  f"A Sub-endpoint for endpoint '{self._endpoint_name}' must be "
144
- f"one of valid eLabFTW sub-endpoints: {', '.join(ElabFTWURL.VALID_ENDPOINTS[self.endpoint_name])}."
179
+ f"one of valid eLabFTW sub-endpoints: {', '.join(valid_sub_endpoint_name)}."
145
180
  )
146
181
  self._sub_endpoint_name = value
147
182
  else:
@@ -154,7 +189,7 @@ class ElabFTWURL:
154
189
  @endpoint_id.setter
155
190
  def endpoint_id(self, value):
156
191
  if value is not None:
157
- if not re.match(r"^(\d+)$|^(me)$", value := str(value)):
192
+ if not re.match(r"^(\d+)$|^(me)$|^(current)$", value := str(value)):
158
193
  # Although, eLabFTW primarily supports integer-only IDs, there are exceptions, like the alias
159
194
  # ID "me" for receiving one's own user information.
160
195
  raise ElabFTWURLError("Invalid endpoint ID (or entity ID).")
@@ -173,7 +208,7 @@ class ElabFTWURL:
173
208
  "Sub-endpoint ID cannot be defined without first specifying its sub-endpoint name."
174
209
  )
175
210
  if value is not None:
176
- if not re.match(r"^(\d+)$|^(me)$", value := str(value)):
211
+ if not re.match(r"^(\d+)$|^(me)$|^(current)$", value := str(value)):
177
212
  raise ElabFTWURLError("Invalid sub-endpoint ID (or entity sub-ID).")
178
213
  self._sub_endpoint_id = value
179
214
  else:
@@ -191,7 +226,7 @@ class ElabFTWURL:
191
226
 
192
227
  def get(self) -> str:
193
228
  url = (
194
- f"{ElabFTWURL.host}/{self.endpoint_name}/{self.endpoint_id}/"
229
+ f"{self.host}/{self.endpoint_name}/{self.endpoint_id}/"
195
230
  f"{self.sub_endpoint_name}/{self.sub_endpoint_id}"
196
231
  ).rstrip("/")
197
232
  if self.query:
@@ -205,12 +240,14 @@ class GETRequest(APIRequest):
205
240
  def __init__(self, **kwargs):
206
241
  super().__init__(**kwargs)
207
242
 
208
- def _make(self, *args) -> Response:
243
+ def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
209
244
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
210
245
  url = ElabFTWURL(
211
246
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
212
247
  )
213
- return super().client.get(url.get(), headers={"Accept": "application/json"})
248
+ return super().client.get(
249
+ url.get(), headers=headers or {"Accept": "application/json"}, **kwargs
250
+ )
214
251
 
215
252
  def close(self):
216
253
  super().close()
@@ -222,9 +259,15 @@ class GETRequest(APIRequest):
222
259
  sub_endpoint_name: Optional[str] = None,
223
260
  sub_endpoint_id: Union[int, str, None] = None,
224
261
  query: Optional[dict] = None,
262
+ **kwargs,
225
263
  ) -> Response:
226
264
  return super().__call__(
227
- endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
265
+ endpoint_name,
266
+ endpoint_id,
267
+ sub_endpoint_name,
268
+ sub_endpoint_id,
269
+ query,
270
+ **kwargs,
228
271
  )
229
272
 
230
273
 
@@ -234,7 +277,7 @@ class POSTRequest(APIRequest):
234
277
  def __init__(self, **kwargs):
235
278
  super().__init__(**kwargs)
236
279
 
237
- def _make(self, *args, **kwargs) -> Response:
280
+ def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
238
281
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
239
282
  url = ElabFTWURL(
240
283
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
@@ -246,7 +289,8 @@ class POSTRequest(APIRequest):
246
289
  files = kwargs.pop("files", None)
247
290
  return super().client.post(
248
291
  url.get(),
249
- headers={
292
+ headers=headers
293
+ or {
250
294
  "Accept": "*/*",
251
295
  # If json argument isn't empty, '"Content-Type": "application/json"' is automatically set.
252
296
  # '"Content-Type": "multipart/form-data"', takes no effect, and
@@ -284,16 +328,19 @@ class POSTRequest(APIRequest):
284
328
  class AsyncPOSTRequest(APIRequest, is_async_client=True):
285
329
  __slots__ = ()
286
330
 
287
- def __init__(self, **kwargs):
331
+ def __init__(
332
+ self,
333
+ limits=Limits(
334
+ max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
335
+ ),
336
+ **kwargs,
337
+ ):
288
338
  super().__init__(
289
- timeout=180,
290
- limits=Limits(
291
- max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
292
- ),
339
+ limits=limits,
293
340
  **kwargs,
294
341
  )
295
342
 
296
- async def _make(self, *args, **kwargs) -> Response:
343
+ async def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
297
344
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
298
345
  url = ElabFTWURL(
299
346
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
@@ -305,7 +352,7 @@ class AsyncPOSTRequest(APIRequest, is_async_client=True):
305
352
  files = kwargs.pop("files", None)
306
353
  return await super().client.post(
307
354
  url.get(),
308
- headers={"Accept": "*/*"},
355
+ headers=headers or {"Accept": "*/*"},
309
356
  json=data,
310
357
  files=files,
311
358
  **kwargs,
@@ -340,23 +387,25 @@ class AsyncPOSTRequest(APIRequest, is_async_client=True):
340
387
  class AsyncGETRequest(APIRequest, is_async_client=True):
341
388
  __slots__ = ()
342
389
 
343
- def __init__(self, **kwargs):
390
+ def __init__(
391
+ self,
392
+ limits=Limits(
393
+ max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
394
+ ),
395
+ **kwargs,
396
+ ):
344
397
  super().__init__(
345
- timeout=180,
346
- limits=Limits(
347
- max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
348
- ),
398
+ limits=limits,
349
399
  **kwargs,
350
400
  )
351
401
 
352
- async def _make(self, *args) -> Response:
402
+ async def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
353
403
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
354
404
  url = ElabFTWURL(
355
405
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
356
406
  )
357
407
  return await super().client.get(
358
- url.get(),
359
- headers={"Accept": "application/json"},
408
+ url.get(), headers=headers or {"Accept": "application/json"}, **kwargs
360
409
  )
361
410
 
362
411
  async def close(self):
@@ -370,9 +419,15 @@ class AsyncGETRequest(APIRequest, is_async_client=True):
370
419
  sub_endpoint_name: Optional[str] = None,
371
420
  sub_endpoint_id: Union[int, str, None] = None,
372
421
  query: Optional[dict] = None,
422
+ **kwargs,
373
423
  ) -> Response:
374
424
  response = await self._make(
375
- endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
425
+ endpoint_name,
426
+ endpoint_id,
427
+ sub_endpoint_name,
428
+ sub_endpoint_id,
429
+ query,
430
+ **kwargs,
376
431
  )
377
432
  if not self.keep_session_open:
378
433
  await self.close()
@@ -385,7 +440,7 @@ class PATCHRequest(APIRequest):
385
440
  def __init__(self, **kwargs):
386
441
  super().__init__(**kwargs)
387
442
 
388
- def _make(self, *args, **kwargs) -> Response:
443
+ def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
389
444
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
390
445
  url = ElabFTWURL(
391
446
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
@@ -396,7 +451,8 @@ class PATCHRequest(APIRequest):
396
451
  }
397
452
  return super().client.patch(
398
453
  url.get(),
399
- headers={
454
+ headers=headers
455
+ or {
400
456
  "Accept": "application/json"
401
457
  }, # '"Content-Type": "application/json"' is passed automatically when json argument is passed.
402
458
  json=data,
@@ -428,16 +484,19 @@ class PATCHRequest(APIRequest):
428
484
  class AsyncPATCHRequest(APIRequest, is_async_client=True):
429
485
  __slots__ = ()
430
486
 
431
- def __init__(self, **kwargs):
487
+ def __init__(
488
+ self,
489
+ limits=Limits(
490
+ max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
491
+ ),
492
+ **kwargs,
493
+ ):
432
494
  super().__init__(
433
- timeout=180,
434
- limits=Limits(
435
- max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
436
- ),
495
+ limits=limits,
437
496
  **kwargs,
438
497
  )
439
498
 
440
- async def _make(self, *args, **kwargs) -> Response:
499
+ async def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
441
500
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
442
501
  url = ElabFTWURL(
443
502
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
@@ -448,7 +507,7 @@ class AsyncPATCHRequest(APIRequest, is_async_client=True):
448
507
  }
449
508
  return await super().client.patch(
450
509
  url.get(),
451
- headers={"Accept": "application/json"},
510
+ headers=headers or {"Accept": "application/json"},
452
511
  json=data,
453
512
  **kwargs,
454
513
  )
@@ -485,13 +544,15 @@ class DELETERequest(APIRequest):
485
544
  def __init__(self, **kwargs):
486
545
  super().__init__(**kwargs)
487
546
 
488
- def _make(self, *args) -> Response:
547
+ def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
489
548
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
490
549
  url = ElabFTWURL(
491
550
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
492
551
  )
493
552
  return super().client.delete(
494
- url.get(), headers={"Accept": "*/*", "Content-Type": "application/json"}
553
+ url.get(),
554
+ headers=headers or {"Accept": "*/*", "Content-Type": "application/json"},
555
+ **kwargs,
495
556
  )
496
557
 
497
558
  def close(self):
@@ -504,32 +565,42 @@ class DELETERequest(APIRequest):
504
565
  sub_endpoint_name: Optional[str] = None,
505
566
  sub_endpoint_id: Union[int, str, None] = None,
506
567
  query: Optional[dict] = None,
568
+ **kwargs,
507
569
  ) -> Response:
508
570
  return super().__call__(
509
- endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
571
+ endpoint_name,
572
+ endpoint_id,
573
+ sub_endpoint_name,
574
+ sub_endpoint_id,
575
+ query,
576
+ **kwargs,
510
577
  )
511
578
 
512
579
 
513
580
  class AsyncDELETERequest(APIRequest, is_async_client=True):
514
581
  __slots__ = ()
515
582
 
516
- def __init__(self, **kwargs):
583
+ def __init__(
584
+ self,
585
+ limits=Limits(
586
+ max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
587
+ ),
588
+ **kwargs,
589
+ ):
517
590
  super().__init__(
518
- timeout=180,
519
- limits=Limits(
520
- max_connections=100, max_keepalive_connections=30, keepalive_expiry=60
521
- ),
591
+ limits=limits,
522
592
  **kwargs,
523
593
  )
524
594
 
525
- async def _make(self, *args) -> Response:
595
+ async def _make(self, *args, headers: Optional[dict] = None, **kwargs) -> Response:
526
596
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query = args
527
597
  url = ElabFTWURL(
528
598
  endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
529
599
  )
530
600
  return await super().client.delete(
531
601
  url.get(),
532
- headers={"Accept": "*/*", "Content-Type": "application/json"},
602
+ headers=headers or {"Accept": "*/*", "Content-Type": "application/json"},
603
+ **kwargs,
533
604
  )
534
605
 
535
606
  async def close(self):
@@ -543,9 +614,15 @@ class AsyncDELETERequest(APIRequest, is_async_client=True):
543
614
  sub_endpoint_name: Optional[str] = None,
544
615
  sub_endpoint_id: Union[int, str, None] = None,
545
616
  query: Optional[dict] = None,
617
+ **kwargs,
546
618
  ) -> Response:
547
619
  response = await self._make(
548
- endpoint_name, endpoint_id, sub_endpoint_name, sub_endpoint_id, query
620
+ endpoint_name,
621
+ endpoint_id,
622
+ sub_endpoint_name,
623
+ sub_endpoint_id,
624
+ query,
625
+ **kwargs,
549
626
  )
550
627
  if not self.keep_session_open:
551
628
  await self.close()