elapi 2.4.11__tar.gz → 2.4.12__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-2.4.11 → elapi-2.4.12}/PKG-INFO +4 -2
- {elapi-2.4.11 → elapi-2.4.12}/README.md +2 -1
- {elapi-2.4.11 → elapi-2.4.12}/pyproject.toml +3 -2
- elapi-2.4.12/src/elapi/VERSION +1 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_handle_unexp_response.py +14 -2
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_names.py +3 -0
- elapi-2.4.12/src/elapi/api/_supported_versions/5.5.10.json +62 -0
- elapi-2.4.12/src/elapi/api/_supported_versions/5.5.11.json +62 -0
- elapi-2.4.12/src/elapi/api/_supported_versions/5.5.9.json +59 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/path.py +1 -1
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/bill_teams.py +65 -48
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/cli.py +29 -2
- elapi-2.4.11/src/elapi/VERSION +0 -1
- {elapi-2.4.11 → elapi-2.4.12}/LICENSE +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/.DS_Store +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_cache.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_loggers/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_loggers/base.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_loggers/handlers/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_loggers/handlers/base.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_loggers/handlers/callback.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_loggers/handlers/stderr.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_core_init/_utils.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_names.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_vendor/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_vendor/haggis/LICENSE +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_vendor/haggis/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_vendor/haggis/logs.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/_vendor/haggis/version.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_openapi_parser.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.1.15.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.0.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.1.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.2.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.3.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.4.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.5.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.6.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.7.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.2.8.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.0.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.1.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.10.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.11.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.2.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.3.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.4.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.5.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.6.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.7.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.8.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.3.9.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.4.0.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.4.1.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.4.2.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.4.3.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.4.4.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.4.5.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.4.6.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.0.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.1.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.2.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.3.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.4.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.5.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.6.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.7.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/_supported_versions/5.5.8.json +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/api.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/endpoint.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/api/validators.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/cli/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/cli/__main__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/cli/_plugin_handler.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/cli/_venv_state_manager.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/cli/doc.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/cli/elapi.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/configuration/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/configuration/_config_history.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/configuration/_overload_history.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/configuration/config.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/configuration/overridable_vars.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/configuration/validators.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/core_validators/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/core_validators/base.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/core_validators/path.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/loggers/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/loggers/base.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/loggers/handlers/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/loggers/handlers/file.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/loggers/log_file.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/.DS_Store +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/_doc.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/configuration.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/formats.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/generate_table.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/logger.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/names.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/registry.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/specification.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/utils.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/bill_teams/validators.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/cli.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/cli_helpers.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/export.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/get_data_from_input_or_path.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/get_information.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/get_location_from_headers.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/commons/get_whoami.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/experiments/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/experiments/_doc.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/experiments/cli.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/experiments/experiments.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/experiments/formats.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/mail/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/mail/_yagmail_patch.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/mail/cli.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/mail/configuration.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/mail/mail.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/mail/names.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/mail/yagmail_configuration.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/plugins/show_config.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/_markdown_doc.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/_missing.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/base.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/colors.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/formats.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/highlight.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/styles/rich_utils.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/utils/__init__.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/utils/messages.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/utils/typer_patches.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/utils/utils.py +0 -0
- {elapi-2.4.11 → elapi-2.4.12}/src/elapi/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elapi
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.12
|
|
4
4
|
Summary: elAPI is a powerful, extensible API client for eLabFTW.
|
|
5
5
|
Keywords: elabftw,api
|
|
6
6
|
Author: Alexander Haller, Mahadi Xion
|
|
@@ -33,6 +33,7 @@ Requires-Dist: httpx-aiohttp>=0.1.12,<0.2
|
|
|
33
33
|
Requires-Dist: pydantic>=2.12.3
|
|
34
34
|
Requires-Dist: urllib3>=2.6.3
|
|
35
35
|
Requires-Dist: aiohttp>=3.13.5
|
|
36
|
+
Requires-Dist: lxml>=6.1.0
|
|
36
37
|
Requires-Dist: jinja2>=3.1.6,<3.2 ; extra == 'mail'
|
|
37
38
|
Requires-Dist: nameparser>=1.1.3 ; extra == 'mail'
|
|
38
39
|
Requires-Dist: yagmail>=0.15.293,<0.16 ; extra == 'mail'
|
|
@@ -148,7 +149,8 @@ your configuration details.
|
|
|
148
149
|
|
|
149
150
|
elAPI is compatible with the following Python versions: `3.11`, `3.12`, `3.13`, `3.14`. elAPI supports
|
|
150
151
|
the [eLabFTW REST API v2](https://doc.elabftw.net/api/v2/), and can be used with the following eLabFTW
|
|
151
|
-
versions: `5.5.
|
|
152
|
+
versions: `5.5.11`, `5.5.10`, `5.5.9`, `5.5.8`, `5.5.7`, `5.5.6`, `5.5.5`, `5.5.4`, `5.5.3`, `5.5.2`, `5.5.1`, `5.5.0`,
|
|
153
|
+
`5.4.6`, `5.4.5`, `5.4.4`, `5.4.3`, `5.4.2`, `5.4.1`,
|
|
152
154
|
`5.4.0`, `5.3.11`, `5.3.10`, `5.3.9`, `5.3.8`, `5.3.7`, `5.3.6`,
|
|
153
155
|
`5.3.5`, `5.3.4`, `5.3.3`, `5.3.2`, `5.3.1`, `5.3.0`, `5.2.8`, `5.2.7`, `5.2.6`, `5.2.5`, `5.2.4`,
|
|
154
156
|
`5.2.3`, `5.2.2`, `5.2.1`, `5.2.0`, `5.1.1`.
|
|
@@ -96,7 +96,8 @@ your configuration details.
|
|
|
96
96
|
|
|
97
97
|
elAPI is compatible with the following Python versions: `3.11`, `3.12`, `3.13`, `3.14`. elAPI supports
|
|
98
98
|
the [eLabFTW REST API v2](https://doc.elabftw.net/api/v2/), and can be used with the following eLabFTW
|
|
99
|
-
versions: `5.5.
|
|
99
|
+
versions: `5.5.11`, `5.5.10`, `5.5.9`, `5.5.8`, `5.5.7`, `5.5.6`, `5.5.5`, `5.5.4`, `5.5.3`, `5.5.2`, `5.5.1`, `5.5.0`,
|
|
100
|
+
`5.4.6`, `5.4.5`, `5.4.4`, `5.4.3`, `5.4.2`, `5.4.1`,
|
|
100
101
|
`5.4.0`, `5.3.11`, `5.3.10`, `5.3.9`, `5.3.8`, `5.3.7`, `5.3.6`,
|
|
101
102
|
`5.3.5`, `5.3.4`, `5.3.3`, `5.3.2`, `5.3.1`, `5.3.0`, `5.2.8`, `5.2.7`, `5.2.6`, `5.2.5`, `5.2.4`,
|
|
102
103
|
`5.2.3`, `5.2.2`, `5.2.1`, `5.2.0`, `5.1.1`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "elapi"
|
|
3
|
-
version = "2.4.
|
|
3
|
+
version = "2.4.12"
|
|
4
4
|
description = "elAPI is a powerful, extensible API client for eLabFTW."
|
|
5
5
|
authors = [{ name = "Alexander Haller, Mahadi Xion", email = "elabftw@uni-heidelberg.de" }]
|
|
6
6
|
requires-python = ">=3.11.0,<4"
|
|
@@ -40,6 +40,7 @@ dependencies = [
|
|
|
40
40
|
# Used by yagmail, but added explicitly here to get an important security update
|
|
41
41
|
"aiohttp>=3.13.5",
|
|
42
42
|
# Used by httpx-aiohttp, but added explicitly here to get an important security update
|
|
43
|
+
"lxml>=6.1.0",
|
|
43
44
|
]
|
|
44
45
|
license-files = [
|
|
45
46
|
"LICENSE",
|
|
@@ -74,7 +75,7 @@ build-backend = "uv_build"
|
|
|
74
75
|
[dependency-groups]
|
|
75
76
|
dev = [
|
|
76
77
|
"mypy>=1.18.2",
|
|
77
|
-
"pip
|
|
78
|
+
"pip>26.0.1", # vendy requires pip
|
|
78
79
|
"ptpython>=3.0.23,<4",
|
|
79
80
|
"vendy>=0.0.3",
|
|
80
81
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.4.12
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from typing import TypedDict
|
|
2
3
|
|
|
3
4
|
from ..loggers import Logger
|
|
4
5
|
from ..utils import UnexpectedAPIResponseType
|
|
5
6
|
from ._names import ElabUserGroups
|
|
6
7
|
|
|
7
8
|
logger = Logger()
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _ElabUserTeamsDict(TypedDict, total=False):
|
|
12
|
+
id: int
|
|
13
|
+
name: str
|
|
14
|
+
usergroup: int
|
|
15
|
+
is_admin: bool
|
|
16
|
+
is_owner: bool
|
|
17
|
+
is_archived: bool
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
NewUserTeamsType = list[_ElabUserTeamsDict]
|
|
9
21
|
|
|
10
22
|
_DEBUG_LOG_EMIT_ONCE: bool = False
|
|
11
23
|
|
|
@@ -13,7 +25,7 @@ _DEBUG_LOG_EMIT_ONCE: bool = False
|
|
|
13
25
|
def handle_new_user_teams(user_teams: str | NewUserTeamsType) -> NewUserTeamsType:
|
|
14
26
|
global _DEBUG_LOG_EMIT_ONCE
|
|
15
27
|
|
|
16
|
-
struct_user_teams: list[
|
|
28
|
+
struct_user_teams: list[_ElabUserTeamsDict] = []
|
|
17
29
|
match user_teams:
|
|
18
30
|
case list():
|
|
19
31
|
struct_user_teams = user_teams
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apikeys": [],
|
|
3
|
+
"compounds": [],
|
|
4
|
+
"config": [],
|
|
5
|
+
"dspace": [],
|
|
6
|
+
"event": [],
|
|
7
|
+
"events": [],
|
|
8
|
+
"experiments": [
|
|
9
|
+
"comments",
|
|
10
|
+
"compounds_links",
|
|
11
|
+
"experiments_links",
|
|
12
|
+
"items_links",
|
|
13
|
+
"revisions",
|
|
14
|
+
"steps",
|
|
15
|
+
"tags",
|
|
16
|
+
"uploads"
|
|
17
|
+
],
|
|
18
|
+
"experiments_templates": [
|
|
19
|
+
"revisions",
|
|
20
|
+
"steps",
|
|
21
|
+
"tags"
|
|
22
|
+
],
|
|
23
|
+
"exports": [],
|
|
24
|
+
"extra_fields_keys": [],
|
|
25
|
+
"favtags": [],
|
|
26
|
+
"idps": [
|
|
27
|
+
"certs",
|
|
28
|
+
"endpoints"
|
|
29
|
+
],
|
|
30
|
+
"idps_sources": [],
|
|
31
|
+
"import": [],
|
|
32
|
+
"info": [],
|
|
33
|
+
"items": [
|
|
34
|
+
"comments",
|
|
35
|
+
"compounds_links",
|
|
36
|
+
"experiments_links",
|
|
37
|
+
"items_links",
|
|
38
|
+
"revisions",
|
|
39
|
+
"steps",
|
|
40
|
+
"tags",
|
|
41
|
+
"uploads"
|
|
42
|
+
],
|
|
43
|
+
"items_types": [
|
|
44
|
+
"steps",
|
|
45
|
+
"tags"
|
|
46
|
+
],
|
|
47
|
+
"reports": [],
|
|
48
|
+
"storage_units": [],
|
|
49
|
+
"teams": [
|
|
50
|
+
"experiments_categories",
|
|
51
|
+
"experiments_status",
|
|
52
|
+
"items_status",
|
|
53
|
+
"resources_categories",
|
|
54
|
+
"teamgroups"
|
|
55
|
+
],
|
|
56
|
+
"todolist": [],
|
|
57
|
+
"unfinished_steps": [],
|
|
58
|
+
"users": [
|
|
59
|
+
"notifications",
|
|
60
|
+
"uploads"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apikeys": [],
|
|
3
|
+
"compounds": [],
|
|
4
|
+
"config": [],
|
|
5
|
+
"dspace": [],
|
|
6
|
+
"event": [],
|
|
7
|
+
"events": [],
|
|
8
|
+
"experiments": [
|
|
9
|
+
"comments",
|
|
10
|
+
"compounds_links",
|
|
11
|
+
"experiments_links",
|
|
12
|
+
"items_links",
|
|
13
|
+
"revisions",
|
|
14
|
+
"steps",
|
|
15
|
+
"tags",
|
|
16
|
+
"uploads"
|
|
17
|
+
],
|
|
18
|
+
"experiments_templates": [
|
|
19
|
+
"revisions",
|
|
20
|
+
"steps",
|
|
21
|
+
"tags"
|
|
22
|
+
],
|
|
23
|
+
"exports": [],
|
|
24
|
+
"extra_fields_keys": [],
|
|
25
|
+
"favtags": [],
|
|
26
|
+
"idps": [
|
|
27
|
+
"certs",
|
|
28
|
+
"endpoints"
|
|
29
|
+
],
|
|
30
|
+
"idps_sources": [],
|
|
31
|
+
"import": [],
|
|
32
|
+
"info": [],
|
|
33
|
+
"items": [
|
|
34
|
+
"comments",
|
|
35
|
+
"compounds_links",
|
|
36
|
+
"experiments_links",
|
|
37
|
+
"items_links",
|
|
38
|
+
"revisions",
|
|
39
|
+
"steps",
|
|
40
|
+
"tags",
|
|
41
|
+
"uploads"
|
|
42
|
+
],
|
|
43
|
+
"items_types": [
|
|
44
|
+
"steps",
|
|
45
|
+
"tags"
|
|
46
|
+
],
|
|
47
|
+
"reports": [],
|
|
48
|
+
"storage_units": [],
|
|
49
|
+
"teams": [
|
|
50
|
+
"experiments_categories",
|
|
51
|
+
"experiments_status",
|
|
52
|
+
"items_status",
|
|
53
|
+
"resources_categories",
|
|
54
|
+
"teamgroups"
|
|
55
|
+
],
|
|
56
|
+
"todolist": [],
|
|
57
|
+
"unfinished_steps": [],
|
|
58
|
+
"users": [
|
|
59
|
+
"notifications",
|
|
60
|
+
"uploads"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apikeys": [],
|
|
3
|
+
"compounds": [],
|
|
4
|
+
"config": [],
|
|
5
|
+
"dspace": [],
|
|
6
|
+
"event": [],
|
|
7
|
+
"events": [],
|
|
8
|
+
"experiments": [
|
|
9
|
+
"comments",
|
|
10
|
+
"compounds_links",
|
|
11
|
+
"experiments_links",
|
|
12
|
+
"items_links",
|
|
13
|
+
"revisions",
|
|
14
|
+
"steps",
|
|
15
|
+
"tags",
|
|
16
|
+
"uploads"
|
|
17
|
+
],
|
|
18
|
+
"experiments_templates": [
|
|
19
|
+
"revisions",
|
|
20
|
+
"steps",
|
|
21
|
+
"tags"
|
|
22
|
+
],
|
|
23
|
+
"exports": [],
|
|
24
|
+
"extra_fields_keys": [],
|
|
25
|
+
"favtags": [],
|
|
26
|
+
"idps": [],
|
|
27
|
+
"idps_sources": [],
|
|
28
|
+
"import": [],
|
|
29
|
+
"info": [],
|
|
30
|
+
"items": [
|
|
31
|
+
"comments",
|
|
32
|
+
"compounds_links",
|
|
33
|
+
"experiments_links",
|
|
34
|
+
"items_links",
|
|
35
|
+
"revisions",
|
|
36
|
+
"steps",
|
|
37
|
+
"tags",
|
|
38
|
+
"uploads"
|
|
39
|
+
],
|
|
40
|
+
"items_types": [
|
|
41
|
+
"steps",
|
|
42
|
+
"tags"
|
|
43
|
+
],
|
|
44
|
+
"reports": [],
|
|
45
|
+
"storage_units": [],
|
|
46
|
+
"teams": [
|
|
47
|
+
"experiments_categories",
|
|
48
|
+
"experiments_status",
|
|
49
|
+
"items_status",
|
|
50
|
+
"resources_categories",
|
|
51
|
+
"teamgroups"
|
|
52
|
+
],
|
|
53
|
+
"todolist": [],
|
|
54
|
+
"unfinished_steps": [],
|
|
55
|
+
"users": [
|
|
56
|
+
"notifications",
|
|
57
|
+
"uploads"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -240,7 +240,7 @@ class ProperPath:
|
|
|
240
240
|
f"Could not open file {self._error_helper_compare_path_source(self.name, file)}. "
|
|
241
241
|
f"Exception: {e!r}"
|
|
242
242
|
)
|
|
243
|
-
self.PathException = e
|
|
243
|
+
self.PathException = e.__class__
|
|
244
244
|
raise e
|
|
245
245
|
except (os_exception := OSError) as e:
|
|
246
246
|
self.err_logger.debug(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
1
2
|
from datetime import datetime
|
|
3
|
+
from functools import cached_property
|
|
2
4
|
from pathlib import Path
|
|
3
|
-
from typing import Union
|
|
5
|
+
from typing import TypedDict, Union
|
|
4
6
|
|
|
5
7
|
from dateutil import parser
|
|
6
8
|
from dateutil.relativedelta import relativedelta
|
|
@@ -14,6 +16,30 @@ from ..commons import AsyncInformation, Information, RecursiveInformation
|
|
|
14
16
|
logger = Logger()
|
|
15
17
|
|
|
16
18
|
|
|
19
|
+
class TeamMembersDict(TypedDict):
|
|
20
|
+
firstname: str
|
|
21
|
+
lastname: str
|
|
22
|
+
email: str
|
|
23
|
+
usergroup: str
|
|
24
|
+
is_archived: bool
|
|
25
|
+
is_expired: bool
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TeamsDict(TypedDict, total=False):
|
|
29
|
+
active_member_count: int
|
|
30
|
+
admins: dict[str, TeamMembersDict]
|
|
31
|
+
members: dict[str, TeamMembersDict]
|
|
32
|
+
on_trial: bool
|
|
33
|
+
team_created_at: str
|
|
34
|
+
team_id: int
|
|
35
|
+
team_name: str
|
|
36
|
+
total_archived_member_count: int
|
|
37
|
+
total_expired_member_count: int
|
|
38
|
+
total_member_count: int
|
|
39
|
+
total_unarchived_member_count: int
|
|
40
|
+
trial_ends_at: str
|
|
41
|
+
|
|
42
|
+
|
|
17
43
|
class UsersInformation:
|
|
18
44
|
__slots__ = "users", "user_id_prefix"
|
|
19
45
|
endpoint_name = "users"
|
|
@@ -27,6 +53,7 @@ class UsersInformation:
|
|
|
27
53
|
)
|
|
28
54
|
for user in all_users:
|
|
29
55
|
if (user_teams := user.get("teams")) is not None:
|
|
56
|
+
# noinspection PyTypeChecker
|
|
30
57
|
if handle_new_user_teams(user_teams) is not None:
|
|
31
58
|
return all_users
|
|
32
59
|
return await fallback_all_users.items()
|
|
@@ -102,7 +129,7 @@ class OwnersInformation:
|
|
|
102
129
|
|
|
103
130
|
|
|
104
131
|
class TeamsList:
|
|
105
|
-
__slots__ = "users", "teams", "contract"
|
|
132
|
+
__slots__ = "users", "teams", "contract", "__dict__"
|
|
106
133
|
|
|
107
134
|
def __init__(
|
|
108
135
|
self,
|
|
@@ -112,16 +139,12 @@ class TeamsList:
|
|
|
112
139
|
self.users = users_information
|
|
113
140
|
self.teams = teams_information
|
|
114
141
|
|
|
115
|
-
@
|
|
142
|
+
@cached_property
|
|
116
143
|
def BILL_RUN_DATE(self) -> datetime:
|
|
144
|
+
# cached_property is used to make sure that the same
|
|
145
|
+
# current datetime is used for all comparisons
|
|
117
146
|
return datetime.now()
|
|
118
147
|
|
|
119
|
-
@BILL_RUN_DATE.setter
|
|
120
|
-
def BILL_RUN_DATE(self, value):
|
|
121
|
-
raise AttributeError(
|
|
122
|
-
"BILL_RUN_DATE is always the current date. It cannot be modified!"
|
|
123
|
-
)
|
|
124
|
-
|
|
125
148
|
@property
|
|
126
149
|
def LAUNCH_DATE(self) -> datetime:
|
|
127
150
|
return datetime(2023, 8, 1, 0, 0, 0)
|
|
@@ -141,62 +164,44 @@ class TeamsList:
|
|
|
141
164
|
return True
|
|
142
165
|
return False
|
|
143
166
|
|
|
144
|
-
def
|
|
167
|
+
def _get_teams(self, admins_only: bool = False) -> dict:
|
|
145
168
|
# Generate teams information with team owners
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
# noinspection PyTypeChecker
|
|
170
|
+
team_members: defaultdict[str, dict[str, TeamMembersDict]] = defaultdict(dict)
|
|
171
|
+
# noinspection PyTypeChecker
|
|
172
|
+
admins: defaultdict[str, dict[str, TeamMembersDict]] = defaultdict(dict)
|
|
173
|
+
teams: dict[str, TeamsDict] = {}
|
|
149
174
|
for u in self.users:
|
|
150
175
|
user_teams = handle_new_user_teams(u["teams"])
|
|
151
|
-
for team in user_teams: # O(n^2):
|
|
176
|
+
for team in user_teams: # O(n^2): we iterate over the "teams" field
|
|
152
177
|
uid = u["userid"]
|
|
153
178
|
# Get teams user count
|
|
154
179
|
team_id = str(team["id"])
|
|
155
|
-
|
|
156
|
-
|
|
180
|
+
team_members[team_id].update(
|
|
181
|
+
{
|
|
157
182
|
uid: {
|
|
158
183
|
"firstname": u["firstname"],
|
|
159
184
|
"lastname": u["lastname"],
|
|
160
185
|
"email": u["email"],
|
|
161
186
|
"usergroup": team["usergroup"],
|
|
162
|
-
"
|
|
187
|
+
"is_archived": bool(team["is_archived"]),
|
|
188
|
+
"is_expired": self.is_user_expired(user_data=u),
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
)
|
|
192
|
+
if team["usergroup"] == ElabUserGroups.admin:
|
|
193
|
+
admins[team_id].update(
|
|
167
194
|
{
|
|
168
195
|
uid: {
|
|
169
196
|
"firstname": u["firstname"],
|
|
170
197
|
"lastname": u["lastname"],
|
|
171
198
|
"email": u["email"],
|
|
172
199
|
"usergroup": team["usergroup"],
|
|
173
|
-
"
|
|
200
|
+
"is_archived": bool(team["is_archived"]),
|
|
201
|
+
"is_expired": self.is_user_expired(user_data=u),
|
|
174
202
|
}
|
|
175
203
|
}
|
|
176
204
|
)
|
|
177
|
-
if team["usergroup"] == ElabUserGroups.admin:
|
|
178
|
-
if not admins.get(team_id):
|
|
179
|
-
admins[team_id] = {
|
|
180
|
-
uid: {
|
|
181
|
-
"firstname": u["firstname"],
|
|
182
|
-
"lastname": u["lastname"],
|
|
183
|
-
"email": u["email"],
|
|
184
|
-
"usergroup": team["usergroup"],
|
|
185
|
-
"expired": self.is_user_expired(user_data=u),
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else:
|
|
189
|
-
admins[team_id].update(
|
|
190
|
-
{
|
|
191
|
-
uid: {
|
|
192
|
-
"firstname": u["firstname"],
|
|
193
|
-
"lastname": u["lastname"],
|
|
194
|
-
"email": u["email"],
|
|
195
|
-
"usergroup": team["usergroup"],
|
|
196
|
-
"expired": self.is_user_expired(user_data=u),
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
)
|
|
200
205
|
# Get team basic information
|
|
201
206
|
teams[team_id] = {}
|
|
202
207
|
teams[team_id]["team_name"] = team["name"]
|
|
@@ -214,10 +219,22 @@ class TeamsList:
|
|
|
214
219
|
teams[team_id]["members"] = {}
|
|
215
220
|
teams[team_id]["members"] = team_members[team_id]
|
|
216
221
|
teams[team_id]["admins"] = admins.get(team_id, {})
|
|
217
|
-
teams[team_id]["
|
|
218
|
-
teams[team_id]["
|
|
222
|
+
teams[team_id]["total_member_count"] = len(team_members[team_id])
|
|
223
|
+
teams[team_id]["total_unarchived_member_count"] = 0
|
|
224
|
+
teams[team_id]["total_archived_member_count"] = 0
|
|
225
|
+
teams[team_id]["total_expired_member_count"] = 0
|
|
226
|
+
teams[team_id]["active_member_count"] = 0
|
|
219
227
|
for k in team_members[team_id]:
|
|
220
|
-
if team_members[team_id][k]["
|
|
228
|
+
if not team_members[team_id][k]["is_archived"]:
|
|
229
|
+
teams[team_id]["total_unarchived_member_count"] += 1
|
|
230
|
+
if team_members[team_id][k]["is_archived"]:
|
|
231
|
+
teams[team_id]["total_archived_member_count"] += 1
|
|
232
|
+
if team_members[team_id][k]["is_expired"]:
|
|
233
|
+
teams[team_id]["total_expired_member_count"] += 1
|
|
234
|
+
if not (
|
|
235
|
+
team_members[team_id][k]["is_expired"]
|
|
236
|
+
or team_members[team_id][k]["is_archived"]
|
|
237
|
+
):
|
|
221
238
|
teams[team_id]["active_member_count"] += 1
|
|
222
239
|
# Add trial information
|
|
223
240
|
trial_starts_at = self.team_trial_start_date(
|
|
@@ -225,12 +242,12 @@ class TeamsList:
|
|
|
225
242
|
)
|
|
226
243
|
trial_ends_at = trial_starts_at + self.TRIAL_PERIOD
|
|
227
244
|
teams[team_id]["trial_ends_at"] = str(trial_ends_at)
|
|
228
|
-
teams[team_id]["on_trial"] = trial_ends_at >
|
|
245
|
+
teams[team_id]["on_trial"] = trial_ends_at > self.BILL_RUN_DATE
|
|
229
246
|
|
|
230
247
|
return teams
|
|
231
248
|
|
|
232
249
|
def items(self, admins_only: bool = False) -> dict:
|
|
233
|
-
return self.
|
|
250
|
+
return self._get_teams(admins_only)
|
|
234
251
|
|
|
235
252
|
|
|
236
253
|
class OwnersList:
|
|
@@ -9,6 +9,7 @@ from types import NoneType
|
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
11
|
|
|
12
|
+
from ...path import ProperPath
|
|
12
13
|
from ...utils import UnexpectedAPIResponseType
|
|
13
14
|
from .generate_table import is_team_on_trial
|
|
14
15
|
from .names import PLUGIN_NAME, REGISTRY_SUB_PLUGIN_NAME, TARGET_GROUP_NAME
|
|
@@ -135,6 +136,17 @@ else:
|
|
|
135
136
|
# meant to suppress raising the final exception once all attempts have been made
|
|
136
137
|
)
|
|
137
138
|
def get_teams(
|
|
139
|
+
users_data_path: Annotated[
|
|
140
|
+
Optional[str],
|
|
141
|
+
typer.Option(
|
|
142
|
+
"--users-data-path",
|
|
143
|
+
"-U",
|
|
144
|
+
help="File path to users JSON data. If passed, "
|
|
145
|
+
"this will be considered as the source of user data truth. The JSON structure "
|
|
146
|
+
"must match the API response of 'elapi get users'",
|
|
147
|
+
show_default=False,
|
|
148
|
+
),
|
|
149
|
+
] = None,
|
|
138
150
|
admins_only: Annotated[
|
|
139
151
|
bool,
|
|
140
152
|
typer.Option(
|
|
@@ -183,6 +195,16 @@ else:
|
|
|
183
195
|
from ...api.validators import HostIdentityValidator, PermissionValidator
|
|
184
196
|
from ...core_validators import Validate
|
|
185
197
|
|
|
198
|
+
if users_data_path is not None:
|
|
199
|
+
try:
|
|
200
|
+
users_data_path_ = ProperPath(users_data_path)
|
|
201
|
+
except (ValueError, TypeError):
|
|
202
|
+
print_typer_error(
|
|
203
|
+
f"--users-data '{users_data_path}' must be a valid file path."
|
|
204
|
+
)
|
|
205
|
+
raise Exit(1)
|
|
206
|
+
else:
|
|
207
|
+
users_data_path_ = None
|
|
186
208
|
global_session = GlobalSharedSession(
|
|
187
209
|
async_rate_limit=(
|
|
188
210
|
async_rate_limit := _retry_init_handler.get_updated_rate_limit(
|
|
@@ -237,11 +259,16 @@ else:
|
|
|
237
259
|
UsersInformation,
|
|
238
260
|
)
|
|
239
261
|
|
|
240
|
-
|
|
262
|
+
teams_info = TeamsInformation()
|
|
241
263
|
|
|
242
264
|
async def gather_teams_list() -> TeamsList:
|
|
265
|
+
if users_data_path_ is not None:
|
|
266
|
+
with users_data_path_.open("r", encoding="utf-8") as users_data_file:
|
|
267
|
+
users_info = json.load(users_data_file)
|
|
268
|
+
else:
|
|
269
|
+
users_info = await UsersInformation().items()
|
|
243
270
|
try:
|
|
244
|
-
tl = TeamsList(
|
|
271
|
+
tl = TeamsList(users_info, await teams_info.aitems())
|
|
245
272
|
except (RuntimeError, InterruptedError) as error:
|
|
246
273
|
logger.error(
|
|
247
274
|
f"Retrieving users/teams data has failed. Exception details: {error}"
|
elapi-2.4.11/src/elapi/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.4.11
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|