elapi 1.0.7.dev3__tar.gz → 1.0.7.dev5__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.
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/PKG-INFO +35 -5
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/README.md +32 -2
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/pyproject.toml +12 -3
- elapi-1.0.7.dev5/src/elapi/VERSION +1 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/_names.py +3 -0
- elapi-1.0.7.dev5/src/elapi/api/__init__.py +15 -0
- {elapi-1.0.7.dev3/src/elapi → elapi-1.0.7.dev5/src/elapi/api}/api.py +130 -53
- elapi-1.0.7.dev3/src/elapi/validators/permission.py → elapi-1.0.7.dev5/src/elapi/api/validators.py +103 -6
- elapi-1.0.7.dev5/src/elapi/cli/__main__.py +4 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/cli/_plugin_handler.py +4 -1
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/cli/doc.py +19 -1
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/cli/elapi.py +409 -103
- elapi-1.0.7.dev5/src/elapi/configuration/__init__.py +43 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/configuration/_config_history.py +49 -3
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/configuration/config.py +63 -81
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/configuration/log_file.py +1 -1
- elapi-1.0.7.dev5/src/elapi/configuration/overridable_vars.py +45 -0
- elapi-1.0.7.dev5/src/elapi/configuration/validators.py +556 -0
- {elapi-1.0.7.dev3/src/elapi/validators → elapi-1.0.7.dev5/src/elapi/core_validators}/__init__.py +0 -2
- elapi-1.0.7.dev5/src/elapi/plugins/__init__.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/bill_teams/bill_teams.py +4 -4
- elapi-1.0.7.dev5/src/elapi/plugins/bill_teams/cli.py +509 -0
- elapi-1.0.7.dev5/src/elapi/plugins/bill_teams/generate_table.py +13 -0
- elapi-1.0.7.dev5/src/elapi/plugins/bill_teams/specification.py +47 -0
- elapi-1.0.7.dev3/src/elapi/plugins/bill_teams/validator.py → elapi-1.0.7.dev5/src/elapi/plugins/bill_teams/validators.py +168 -32
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/commons/__init__.py +1 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/commons/cli_helpers.py +36 -3
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/commons/export.py +5 -4
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/commons/get_information.py +2 -2
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/commons/get_location_from_headers.py +3 -1
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/experiments/cli.py +19 -21
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/experiments/experiments.py +2 -2
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/show_config.py +87 -13
- elapi-1.0.7.dev5/src/elapi/validators.py +24 -0
- elapi-1.0.7.dev3/src/elapi/VERSION +0 -1
- elapi-1.0.7.dev3/src/elapi/cli/__init__.py +0 -1
- elapi-1.0.7.dev3/src/elapi/cli/__main__.py +0 -3
- elapi-1.0.7.dev3/src/elapi/configuration/__init__.py +0 -23
- elapi-1.0.7.dev3/src/elapi/plugins/bill_teams/cli.py +0 -386
- elapi-1.0.7.dev3/src/elapi/validators/identity.py +0 -163
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/LICENSE +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/__init__.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/_exceptions.py +0 -0
- {elapi-1.0.7.dev3/src/elapi → elapi-1.0.7.dev5/src/elapi/api}/endpoint.py +0 -0
- {elapi-1.0.7.dev3/src/elapi/plugins → elapi-1.0.7.dev5/src/elapi/cli}/__init__.py +0 -0
- {elapi-1.0.7.dev3/src/elapi/validators → elapi-1.0.7.dev5/src/elapi/core_validators}/base.py +0 -0
- {elapi-1.0.7.dev3/src/elapi/validators → elapi-1.0.7.dev5/src/elapi/core_validators}/path.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/loggers/__init__.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/loggers/base.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/loggers/handlers/__init__.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/loggers/handlers/file.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/loggers/handlers/stderr.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/path.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/bill_teams/__init__.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/bill_teams/_doc.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/bill_teams/format.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/bill_teams/invoice.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/experiments/__init__.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/experiments/_doc.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/experiments/formats.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/plugins/version.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/styles/__init__.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/styles/_markdown_doc.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/styles/_missing.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/styles/base.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/styles/colors.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/src/elapi/styles/format.py +0 -0
- {elapi-1.0.7.dev3 → elapi-1.0.7.dev5}/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.
|
|
3
|
+
Version: 1.0.7.dev5
|
|
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,12 @@ 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
|
|
35
|
+
Requires-Dist: tenacity (>=8.4.2,<9.0.0)
|
|
36
36
|
Requires-Dist: typer[all] (>=0.9.0,<0.10.0)
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
38
38
|
|
|
@@ -111,7 +111,7 @@ configuration file present in the currently active directory has the highest pri
|
|
|
111
111
|
directory has the lowest.
|
|
112
112
|
|
|
113
113
|
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`.
|
|
114
|
+
purposes, it would be safe to store everything in `$HOME/.config/elapi.yml`.
|
|
115
115
|
|
|
116
116
|
```yaml
|
|
117
117
|
# elAPI configuration
|
|
@@ -124,12 +124,42 @@ api_token: <token with at least read-access>
|
|
|
124
124
|
# You can generate an API token from eLabFTW user panel -> API keys tab.
|
|
125
125
|
export_dir: ~/Downloads/elAPI
|
|
126
126
|
unsafe_api_token_warning: yes
|
|
127
|
+
enable_http2: no
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
`export_dir` is where elAPI will export response content to if no path is provided to `--export/-E`.
|
|
131
|
+
When `unsafe_api_token_warning` is `True`, elAPI will show a warning if you're storing `elapi.yml` in the current
|
|
132
|
+
working directory, as it typically happens that developers accidentally commit and push configuration files with
|
|
133
|
+
secrets. `enable_http2` enables HTTP/2 protocol support which by default is turned off. Be aware
|
|
134
|
+
of [known issues](https://github.com/encode/httpx/discussions/2112) with
|
|
135
|
+
HTTP/2 if you are making async requests with heavy load.
|
|
136
|
+
|
|
137
|
+
You can get an overview of detected configuration with `elapi show-config`. `show-config` makes it easier to verify
|
|
138
|
+
which configuration values are actually used by elAPI, if you are working with multiple configuration files.
|
|
130
139
|
|
|
131
140
|
```shell
|
|
132
141
|
$ elapi show-config
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
elapi configuration information
|
|
145
|
+
|
|
146
|
+
The following debug information includes configuration values and their sources as detected by elapi.
|
|
147
|
+
|
|
148
|
+
▌ Name [Key]: Value ← Source
|
|
149
|
+
|
|
150
|
+
• Log file path: /Users/<username>/.local/share/elapi/elapi.log
|
|
151
|
+
• Host address [host]: https://demo.elabftw.net/api/v2 ← USER LEVEL
|
|
152
|
+
• API Token [api_token]: 00-55******55555 ← USER LEVEL
|
|
153
|
+
• Export directory [export_dir]: /Users/<username>/Downloads/elapi ← USER LEVEL
|
|
154
|
+
• App data directory: /Users/<username>/.local/share/elapi
|
|
155
|
+
• Caching directory: /private/var/tmp/elapi
|
|
156
|
+
• Unsafe API token use warning [unsafe_api_token_warning]: Yes ← USER LEVEL
|
|
157
|
+
• Enable HTTP/2 [enable_http2]: Yes ← USER LEVEL
|
|
158
|
+
|
|
159
|
+
Detected configuration sources that are in use:
|
|
160
|
+
|
|
161
|
+
• USER LEVEL: /Users/<username>/.config/elapi.yml
|
|
162
|
+
|
|
133
163
|
```
|
|
134
164
|
|
|
135
165
|
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
|
-
|
|
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.
|
|
3
|
+
version = "1.0.7.dev5"
|
|
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
|
-
python-dateutil = "^2.8.2"
|
|
40
|
-
tenacity = "^8.2.3"
|
|
41
39
|
mistune = "^3.0.2"
|
|
40
|
+
h2 = "^4.1.0"
|
|
41
|
+
tenacity = "^8.4.2"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
uhd-urz = ["python-dateutil=^2.8.2", "tenacity=^8.2.3"]
|
|
45
|
+
|
|
46
|
+
[tool.poetry.group.uhd-urz]
|
|
47
|
+
optional = true
|
|
48
|
+
|
|
49
|
+
[tool.poetry.group.uhd-urz.dependencies]
|
|
50
|
+
python-dateutil = "^2.8.2"
|
|
42
51
|
|
|
43
52
|
[tool.poetry.group.dev.dependencies]
|
|
44
53
|
ptpython = "^3.0.23"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.7.dev5
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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"{
|
|
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(
|
|
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,
|
|
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__(
|
|
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
|
-
|
|
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__(
|
|
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
|
-
|
|
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,
|
|
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__(
|
|
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
|
-
|
|
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(),
|
|
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,
|
|
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__(
|
|
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
|
-
|
|
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,
|
|
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()
|