rudi-node-write 1.4.2__tar.gz → 1.5.0__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 (52) hide show
  1. rudi_node_write-1.5.0/PKG-INFO +52 -0
  2. rudi_node_write-1.5.0/pyproject.toml +139 -0
  3. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/connectors/io_rudi_catalog_write.py +7 -4
  4. rudi_node_write-1.4.2/src/rudi_node_write/connectors/io_rudi_manager_write_v3.py → rudi_node_write-1.5.0/src/rudi_node_write/connectors/io_rudi_manager_write.py +67 -32
  5. rudi_node_write-1.5.0/src/rudi_node_write/connectors/io_rudi_manager_write_v2.py +12 -0
  6. rudi_node_write-1.5.0/src/rudi_node_write/connectors/io_rudi_manager_write_v3.py +12 -0
  7. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/connectors/rudi_node_auth.py +4 -1
  8. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_const.py +10 -130
  9. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_meta.py +5 -0
  10. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/file_utils.py +1 -80
  11. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/jwt.py +7 -0
  12. rudi_node_write-1.5.0/src/rudi_node_write.egg-info/PKG-INFO +52 -0
  13. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write.egg-info/SOURCES.txt +0 -2
  14. rudi_node_write-1.5.0/src/rudi_node_write.egg-info/requires.txt +4 -0
  15. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/tests/test_rudi_node_write.py +1 -1
  16. rudi_node_write-1.4.2/PKG-INFO +0 -105
  17. rudi_node_write-1.4.2/pyproject.toml +0 -82
  18. rudi_node_write-1.4.2/requirements-dev.txt +0 -193
  19. rudi_node_write-1.4.2/requirements.txt +0 -7
  20. rudi_node_write-1.4.2/src/rudi_node_write/connectors/io_rudi_manager_write.py +0 -1444
  21. rudi_node_write-1.4.2/src/rudi_node_write/connectors/io_rudi_manager_write_v2.py +0 -124
  22. rudi_node_write-1.4.2/src/rudi_node_write.egg-info/PKG-INFO +0 -105
  23. rudi_node_write-1.4.2/src/rudi_node_write.egg-info/requires.txt +0 -56
  24. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/LICENCE.md +0 -0
  25. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/README.md +0 -0
  26. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/setup.cfg +0 -0
  27. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/__init__.py +0 -0
  28. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/conf/meta_defaults.py +0 -0
  29. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/connectors/io_connector.py +0 -0
  30. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/connectors/io_rudi_jwt_factory.py +0 -0
  31. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/connectors/io_rudi_storage_write.py +0 -0
  32. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_node_writer.py +0 -0
  33. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_contact.py +0 -0
  34. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_dates.py +0 -0
  35. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_dictionary_entry.py +0 -0
  36. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_geo.py +0 -0
  37. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_licence.py +0 -0
  38. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_media.py +0 -0
  39. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_meta_misc.py +0 -0
  40. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/rudi_org.py +0 -0
  41. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/rudi_types/serializable.py +0 -0
  42. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/dict_utils.py +0 -0
  43. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/err.py +0 -0
  44. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/html_utils.py +0 -0
  45. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/list_utils.py +0 -0
  46. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/log.py +0 -0
  47. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/str_utils.py +0 -0
  48. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/type_date.py +0 -0
  49. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/typing_utils.py +0 -0
  50. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write/utils/url_utils.py +0 -0
  51. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write.egg-info/dependency_links.txt +0 -0
  52. {rudi_node_write-1.4.2 → rudi_node_write-1.5.0}/src/rudi_node_write.egg-info/top_level.txt +0 -0
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: rudi-node-write
3
+ Version: 1.5.0
4
+ Summary: Use the internal API of a RUDI Producer node
5
+ Author-email: Olivier Martineau <olivier.martineau@irisa.fr>
6
+ Maintainer-email: Olivier Martineau <olivier.martineau@irisa.fr>
7
+ License-Expression: EUPL-1.2
8
+ Project-URL: Homepage, https://github.com/OlivierMartineau/rudi-node-write
9
+ Project-URL: Documentation, https://app.swaggerhub.com/apis/OlivierMartineau/RudiProducer-InternalAPI
10
+ Project-URL: Repository, https://github.com/OlivierMartineau/rudi-node-write
11
+ Keywords: rudi-node-write,rudi-node-put,RUDI,producer node,RUDI node,open-data,Univ. Rennes
12
+ Classifier: Natural Language :: English
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENCE.md
17
+ Requires-Dist: beautifulsoup4>=4.15.0
18
+ Requires-Dist: chardet>=7.7.0
19
+ Requires-Dist: deepdiff>=9.1.0
20
+ Requires-Dist: puremagic>=1.30
21
+ Dynamic: license-file
22
+
23
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
24
+ [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
25
+
26
+ # RUDI Node tools: _rudi-node-write_ library
27
+
28
+ This library offers tools to take advantage of
29
+ the [internal API](https://app.swaggerhub.com/apis/OlivierMartineau/RudiProducer-InternalAPI) of a RUDI Producer node (
30
+ also
31
+ referred as RUDI node), through the API of the backend of the user interface, the "Producer node manager" or "RUDI Manager" module.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ python3 -m venv .venv
37
+ source .venv/bin/activate
38
+ pip install rudi_node_write
39
+ ```
40
+
41
+ ## Usage: RudiNodeWriter
42
+
43
+ The Jupyter notebook [`README.ipynb`](https://github.com/OlivierMartineau/rudi-node-write/blob/main/README.ipynb) details how to use the library through the [`RudiNodeWriter`](https://github.com/OlivierMartineau/rudi-node-write/blob/main/src/rudi_node_write/rudi_node_writer.py) object.
44
+
45
+ ## Testing
46
+
47
+ The [tests](https://github.com/OlivierMartineau/rudi-node-write/tree/main/tests) can be analyzed for further
48
+ information about how to call the API
49
+
50
+ ```bash
51
+ $ pytest
52
+ ```
@@ -0,0 +1,139 @@
1
+ [project]
2
+ name = "rudi-node-write"
3
+ version = "1.5.0"
4
+ authors = [{ name = "Olivier Martineau", email = "olivier.martineau@irisa.fr" }]
5
+ maintainers = [
6
+ { name = "Olivier Martineau", email = "olivier.martineau@irisa.fr" },
7
+ ]
8
+ description = "Use the internal API of a RUDI Producer node"
9
+ readme = "README.md"
10
+ license-files = ["LICENCE.md"]
11
+ requires-python = ">=3.11"
12
+ license = "EUPL-1.2"
13
+ classifiers = [
14
+ "Natural Language :: English",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ keywords = [
18
+ "rudi-node-write",
19
+ "rudi-node-put",
20
+ "RUDI",
21
+ "producer node",
22
+ "RUDI node",
23
+ "open-data",
24
+ "Univ. Rennes",
25
+ ]
26
+ dependencies = [
27
+ "beautifulsoup4>=4.15.0",
28
+ "chardet>=7.7.0",
29
+ "deepdiff>=9.1.0",
30
+ "puremagic>=1.30",
31
+ ]
32
+
33
+ [dependency-groups]
34
+ dev = ["black==25.1.0", "commitizen==4.8.3", "uv-bump>=0.6.0"]
35
+ build = ["twine==6.1.0"]
36
+ test = ["ipykernel>=7.4.0", "pytest-cov>=7.1.0"]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/OlivierMartineau/rudi-node-write"
40
+ Documentation = "https://app.swaggerhub.com/apis/OlivierMartineau/RudiProducer-InternalAPI"
41
+ Repository = "https://github.com/OlivierMartineau/rudi-node-write"
42
+
43
+ [build-system]
44
+ requires = ["setuptools>=82.0.1"]
45
+ build-backend = "setuptools.build_meta"
46
+
47
+ # ----- Tool: black
48
+ [tool.black]
49
+ line-length = 120
50
+ target-version = ['py311']
51
+
52
+ # ----- Tool: commitizen
53
+ [tool.commitizen]
54
+ name = "cz_conventional_commits"
55
+ version = "1.5.0"
56
+ version_files = ["pyproject.toml:version"]
57
+
58
+ # ----- Tool: pytest
59
+ [tool.pytest.ini_options]
60
+ pythonpath = ["src"]
61
+ norecursedirs = ["*.egg", ".eggs", "dist", "build", "wip"]
62
+ filterwarnings = ["ignore:.*pkg_resources.*:DeprecationWarning"]
63
+ addopts = [
64
+ "--cov=rudi_node_write",
65
+ "--cov-report=term-missing",
66
+ "--cov-report=html:reports/html_dir",
67
+ "--cov-report=xml:reports/coverage.xml",
68
+ ]
69
+
70
+ # ----- Tool: setuptools
71
+ [tool.setuptools.packages.find]
72
+ where = ["src"]
73
+ include = ["rudi_node_write*"]
74
+
75
+ # ----- Tool: pyright
76
+ [tool.pyright]
77
+ exclude = ["**/node_modules", "**/__pycache__"]
78
+ include = ["src"]
79
+ pythonVersion = "3.11"
80
+ reportMissingImports = true
81
+ reportDuplicateImport = true
82
+ reportUnusedImport = true
83
+ reportImportCycles = true
84
+ venvPath = "."
85
+ venv = ".venv"
86
+
87
+ # ----- Tool: tox
88
+ # Single source of truth for the checks run locally and in CI:
89
+ # uv run tox # all environments, serial
90
+ # uv run tox run-parallel # what CI runs (only py312 writes coverage)
91
+ # uv run tox -e py312 # one environment
92
+ [tool.tox]
93
+ env_list = ["py311", "py312", "py313", "py314", "py315", "quality"]
94
+ skip_missing_interpreters = false
95
+
96
+ [tool.tox.env_run_base]
97
+ runner = "uv-venv-lock-runner"
98
+ dependency_groups = ["test"]
99
+ # The interpreter envs exist to prove the suite runs on each version, so only
100
+ # py312 (the dev interpreter, see [tool.tox.env.py312] below) instruments the
101
+ # run. Coverage is off everywhere else: it triples the test time, and measuring
102
+ # it under every version yields no extra information — the badge publishes a
103
+ # single percentage, and the line counts genuinely differ between versions
104
+ # (coverage.py's executable-line detection changed in 3.14), so comparing them
105
+ # compares different denominators.
106
+ commands = [["pytest", "-q", "-p", "no:cacheprovider", "--no-cov"]]
107
+
108
+ # The one env that produces the coverage reports the badge reads. It writes the
109
+ # shared reports/coverage.xml, which is safe precisely because no other env
110
+ # writes one — the interpreter envs run in parallel.
111
+ [tool.tox.env.py312]
112
+ commands = [["pytest", "-q", "-p", "no:cacheprovider"]]
113
+
114
+ [tool.tox.env.quality]
115
+ base_python = ["python3.12"]
116
+ dependency_groups = ["dev", "test"]
117
+ commands = [
118
+ [
119
+ "ruff",
120
+ "check",
121
+ "--no-fix",
122
+ ],
123
+ [
124
+ "black",
125
+ "--check",
126
+ "--diff",
127
+ ".",
128
+ ],
129
+ [
130
+ "pyright",
131
+ ],
132
+ [
133
+ "bandit",
134
+ "-c",
135
+ "pyproject.toml",
136
+ "-r",
137
+ "src",
138
+ ],
139
+ ]
@@ -216,7 +216,7 @@ class RudiNodeCatalogConnector(Connector):
216
216
  def _clean_cache(self) -> None:
217
217
  if self._data_cache is None:
218
218
  self._data_cache = {}
219
- for cached_type in self._data_cache.keys():
219
+ for cached_type in self._data_cache:
220
220
  if time() - (self._data_cache[cached_type][_REFRESH_KEY]) > _DELAY_REFRESH_S:
221
221
  self._data_cache[cached_type] = None
222
222
  # TODO: check the date of the last modified object on source node
@@ -305,9 +305,12 @@ class RudiNodeCatalogConnector(Connector):
305
305
  :param organization_id: the UUID v4 identifier of the organization
306
306
  :return: the deleted organization
307
307
  """
308
- if is_uuid_v4(organization_id) and self.get_producer_with_id(organization_id):
309
- if deleted_org := self.del_admin_api(f"organizations/{organization_id}"):
310
- return RudiOrganization.from_json(deleted_org)
308
+ if (
309
+ is_uuid_v4(organization_id)
310
+ and self.get_producer_with_id(organization_id)
311
+ and (deleted_org := self.del_admin_api(f"organizations/{organization_id}"))
312
+ ):
313
+ return RudiOrganization.from_json(deleted_org)
311
314
  raise HttpErrorNotFound(f"No organization was found with id '{organization_id}'")
312
315
 
313
316
  def delete_org_with_name(self, organization_name: str) -> RudiOrganization:
@@ -6,10 +6,6 @@ from typing import Literal
6
6
  from urllib.parse import quote
7
7
 
8
8
  from rudi_node_write.connectors.io_connector import Connector, https_download
9
- from rudi_node_write.connectors.io_rudi_manager_write import (
10
- CONSOLE_JWT_NAME,
11
- RudiNodeManagerConnector,
12
- )
13
9
  from rudi_node_write.connectors.rudi_node_auth import RudiNodeAuth
14
10
  from rudi_node_write.rudi_types.rudi_const import (
15
11
  MEDIA_TYPE_FILE,
@@ -49,22 +45,28 @@ from rudi_node_write.utils.typing_utils import check_is_int, get_type_name
49
45
  from rudi_node_write.utils.url_utils import ensure_url_startswith
50
46
 
51
47
  # Defaults for constructor
52
- _DEFAULT_USER_AGENT = "RudiNodeManagerConnectorV3"
48
+ _DEFAULT_USER_AGENT = "RudiNodeManagerConnector"
49
+
50
+ # Cookie names
51
+ MANAGER_JWT_NAME = "pmFrontToken"
52
+ CONSOLE_JWT_NAME = "consoleToken"
53
53
 
54
54
  # Manager API prefixes
55
55
  MGR_CATALOG_PREFIX = "catalog"
56
56
  OLD_MGR_CATALOG_PREFIX = "data"
57
57
 
58
+ # Data retrieving, cache
58
59
  REQ_LIMIT = 500
59
60
  _DELAY_REFRESH_S = 60 # seconds
60
61
  _REFRESH_KEY = "refresh_time" # seconds
61
62
 
63
+ # Download status
62
64
  _STATUS_SKIPPED = "skipped"
63
65
  _STATUS_MISSING = "missing"
64
66
  _STATUS_DOWNLOADED = "downloaded"
65
67
 
66
68
 
67
- here = "RudiNodeManagerConnectorV3"
69
+ here = "RudiNodeManagerConnector"
68
70
 
69
71
 
70
72
  def ensure_url_startswith_catalog(url):
@@ -73,13 +75,10 @@ def ensure_url_startswith_catalog(url):
73
75
  return ensure_url_startswith(url, MGR_CATALOG_PREFIX)
74
76
 
75
77
 
76
- JWT = "eyJhbGciOiJFZERTQSJ9.eyJqdGkiOiI3YTc1NzY1OS0wZjJlLTQ1NjItYjc5Yy03OWZjM2IyMTQ5Y2UiLCJpYXQiOjE3NTkxNDczNDcsImV4cCI6MTc2MzI5NDU0Nywic3ViIjoicG9zdG1hbl9vbSIsImNsaWVudF9pZCI6Im9tIiwicmVxX210ZCI6ImFsbCIsInJlcV91cmwiOiJhbGwifQ.VQURaFDBBCCywv7Tsq0eQt_Kes6G1APzjm8awUJp2EXBrCTP5MzRupNISZ79gXhzajX-KH9jPDPxbg1FGzfxAA"
77
-
78
-
79
- class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
78
+ class RudiNodeManagerConnector(Connector):
80
79
  """
81
- Every RUDI node has a UI module called "RUDI node manager", or "Manager" for shorts,
82
- that offers to list, edit or create RUDI metadata, given that you have the required level of access.
80
+ Every RUDI node has a UI module called "RUDI node manager", or "Manager" for shorts.
81
+ It offers to list, edit or create RUDI metadata, given that you have the required level of access.
83
82
  This library takes advantage of the manager backend API to send data and metadata.
84
83
  "GenC" stands for "Generation C", as this particular file takes advantage of the 3rd generation of RUDI node interface.
85
84
  """
@@ -92,28 +91,51 @@ class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
92
91
  headers_user_agent: str = _DEFAULT_USER_AGENT,
93
92
  keep_connection: bool = False,
94
93
  ):
95
- super().__init__(
96
- server_url=server_url,
97
- auth=auth,
98
- name=name,
99
- headers_user_agent=headers_user_agent,
100
- keep_connection=keep_connection,
101
- )
94
+ super().__init__(server_url=server_url, keep_connection=keep_connection)
95
+
96
+ self.server_url = server_url
97
+
98
+ if isinstance(auth, RudiNodeAuth):
99
+ self._auth = auth
100
+ elif isinstance(auth, dict):
101
+ self._auth = RudiNodeAuth(b64url_auth=auth["b64url_auth"], usr=auth["usr"], pwd=auth["pwd"])
102
+ else:
103
+ raise TypeError("Input 'auth' parameter should either be a 'RudiNodeAuth' object or a dict.")
104
+
105
+ self.name = name
106
+ if self.name is None:
107
+ self.name = self.server_url.split(".")[0].split("/")[-1]
108
+
109
+ self._headers_user_agent = headers_user_agent
110
+
102
111
  self._gen = 3
103
112
 
104
- self._cached_api_version = None
105
- self._cached_conf = None
113
+ self._cached_api_version: str | None = None
114
+ self._cached_conf: dict | None = None
115
+
116
+ self._cached_manager_jwt: str | None = None
117
+ self._cached_manager_headers: dict | None = None
106
118
 
107
- self._cached_manager_jwt = None
108
- self._cached_manager_headers = None
119
+ self._cached_storage_url: str | None = None
120
+ self._cached_storage_jwt: str | None = None
121
+ self._cached_storage_connector: RudiNodeStorageConnector | None = None
109
122
 
110
- self._cached_storage_url = None
111
- self._cached_storage_jwt = None
112
- self._cached_storage_connector = None
123
+ self.manager_url: str | None = None
124
+ self.manager_path: str | None = None
125
+
126
+ self._data_cache = {}
113
127
 
114
128
  self.conf
115
129
  # self.test_identified_connection()
116
130
 
131
+ @property
132
+ def _usr(self):
133
+ return self._auth.usr
134
+
135
+ @property
136
+ def _pwd(self):
137
+ return self._auth.pwd
138
+
117
139
  @property
118
140
  def conf(self):
119
141
  if self._cached_conf is None:
@@ -138,6 +160,16 @@ class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
138
160
  raise NotImplementedError("This node version is not compatible with this object") from e
139
161
  return self._cached_conf
140
162
 
163
+ @property
164
+ def storage_url(self) -> str:
165
+ return self._cached_storage_url or self.conf["storagePubUrl"]
166
+
167
+ @property
168
+ def portal_url(self) -> str | None:
169
+ if self.node_urls is not None:
170
+ return self.node_urls.get("portal_url")
171
+ return None
172
+
141
173
  # ----------[ Basic API calls ]-------------------------------------------------------------------------------------
142
174
  def get_api(self, url: str, headers: dict, keep_alive: bool = False):
143
175
  """
@@ -303,7 +335,7 @@ class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
303
335
  @property
304
336
  def storage_connector(self):
305
337
  if self._cached_storage_connector is None:
306
- self._cached_storage_connector = RudiNodeStorageConnectorV3(self.storage_url, self._storage_jwt)
338
+ self._cached_storage_connector = RudiNodeStorageConnector(self.storage_url, self._storage_jwt)
307
339
  if is_jwt_expired(self._cached_storage_connector.jwt):
308
340
  self._cached_storage_connector.jwt = self._storage_jwt
309
341
  return self._cached_storage_connector
@@ -417,7 +449,7 @@ class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
417
449
  :param obj_type: one of RUDI object types
418
450
  :return: the list of objects for this type
419
451
  """
420
- here = f"{self.class_name}.get_data"
452
+ here = f"{self.class_name}.get_catalog_cached"
421
453
 
422
454
  if obj_type in RUDI_OBJECT_TYPES:
423
455
  if not (self._gen == 1 and obj_type == "media"):
@@ -428,6 +460,9 @@ class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
428
460
  else:
429
461
  return self.get_cache(ensure_url_startswith_catalog(obj_type))
430
462
 
463
+ def get_data(self, obj_type: RudiObjectTypeStr | str) -> list[dict] | dict: # type: ignore
464
+ return self.get_catalog_cached(obj_type)
465
+
431
466
  # ----------[ Requesting data ]-------------------------------------------------------------------------------------
432
467
 
433
468
  def _count_obj(self, obj_type: RudiObjectTypeStr | str) -> int:
@@ -1225,7 +1260,7 @@ class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
1225
1260
  return ack
1226
1261
 
1227
1262
 
1228
- class RudiNodeStorageConnectorV3(Connector):
1263
+ class RudiNodeStorageConnector(Connector):
1229
1264
  def __init__(self, server_url: str, jwt: str):
1230
1265
  super().__init__(server_url)
1231
1266
  self.jwt = jwt
@@ -1407,14 +1442,14 @@ class FileTooBigException(Exception):
1407
1442
 
1408
1443
  if __name__ == "__main__": # pragma: no cover
1409
1444
  begin = time()
1410
- tests = "RudiNodeManagerConnectorV3 tests"
1445
+ tests = "RudiNodeManagerConnector tests"
1411
1446
  creds_file = "../creds/creds_pytest.json"
1412
1447
  rudi_node_creds = read_json_file(creds_file)
1413
1448
  manager_url = slash_join(rudi_node_creds["url"], "manager")
1414
1449
  log_d(tests, "node_url", manager_url)
1415
1450
  auth = RudiNodeAuth.from_json(rudi_node_creds)
1416
1451
 
1417
- manager = RudiNodeManagerConnectorV3(server_url=manager_url, auth=auth)
1452
+ manager = RudiNodeManagerConnector(server_url=manager_url, auth=auth)
1418
1453
  log_d(tests, "catalog_url", manager.catalog_url)
1419
1454
  log_d(tests, "storage_url", manager.storage_url)
1420
1455
  log_d(tests, "manager_url", manager.manager_url)
@@ -1443,7 +1478,7 @@ if __name__ == "__main__": # pragma: no cover
1443
1478
  log_d(tests, "rudi_media_url", storage_url)
1444
1479
 
1445
1480
  # "Hack" when no SSL connection is available
1446
- storage = RudiNodeStorageConnectorV3(server_url=storage_url, jwt=manager._storage_jwt)
1481
+ storage = RudiNodeStorageConnector(server_url=storage_url, jwt=manager._storage_jwt)
1447
1482
  storage.scheme = "http"
1448
1483
  storage.should_log_response = True
1449
1484
  manager._cached_storage_connector = storage
@@ -0,0 +1,12 @@
1
+ from rudi_node_write.connectors.io_rudi_manager_write import (
2
+ RudiNodeManagerConnector,
3
+ RudiNodeStorageConnector,
4
+ )
5
+
6
+
7
+ class RudiNodeManagerConnectorV2(RudiNodeManagerConnector):
8
+ pass
9
+
10
+
11
+ class RudiNodeStorageConnectorV2(RudiNodeStorageConnector):
12
+ pass
@@ -0,0 +1,12 @@
1
+ from rudi_node_write.connectors.io_rudi_manager_write import (
2
+ RudiNodeManagerConnector,
3
+ RudiNodeStorageConnector,
4
+ )
5
+
6
+
7
+ class RudiNodeManagerConnectorV3(RudiNodeManagerConnector):
8
+ pass
9
+
10
+
11
+ class RudiNodeStorageConnectorV3(RudiNodeStorageConnector):
12
+ pass
@@ -2,6 +2,7 @@ from base64 import urlsafe_b64decode, urlsafe_b64encode
2
2
 
3
3
  from rudi_node_write.rudi_types.serializable import Serializable
4
4
  from rudi_node_write.utils.jwt import is_base64_url, pad_b64_str
5
+ from rudi_node_write.utils.log import log_d
5
6
 
6
7
 
7
8
  class RudiNodeAuth(Serializable):
@@ -16,12 +17,14 @@ class RudiNodeAuth(Serializable):
16
17
  - either a `b64url_auth` URL-safe Base64 encoded `usr:pwd` string
17
18
  - or with a couple of `usr` and `pwd` strings
18
19
  """
20
+ here = "RudiNodeAuth"
19
21
  if b64url_auth is not None:
20
22
  self._b64_auth = pad_b64_str(b64url_auth)
23
+ log_d(here, "self._b64_auth", self._b64_auth)
21
24
  if not is_base64_url(self._b64_auth):
22
25
  raise ValueError("The input `b64url_auth` should be a urlsafe-base64 string.")
23
26
  try:
24
- self.usr, self.pwd = urlsafe_b64decode(b64url_auth).decode("utf-8").split(":")
27
+ self.usr, self.pwd = urlsafe_b64decode(self._b64_auth).decode("utf-8").split(":")
25
28
  except Exception as e:
26
29
  raise ValueError(
27
30
  "The input `b64url_auth` should be a urlsafe-base64 string that encodes a `usr:pwd` string couple"
@@ -109,6 +109,7 @@ FileExtensions: Final = {
109
109
  ".bmp": "image/bmp",
110
110
  ".bz": "application/x-bzip",
111
111
  ".bz2": "application/x-bzip2",
112
+ ".cdf": "application/netcdf",
112
113
  ".css": "text/css",
113
114
  ".csv": "text/csv",
114
115
  ".doc": "application/msword",
@@ -118,6 +119,7 @@ FileExtensions: Final = {
118
119
  ".flif": "image/flif",
119
120
  ".geojson": "application/geo+json",
120
121
  ".gif": "image/gif",
122
+ ".gpkg": "application/geopackage+sqlite3",
121
123
  ".gz": "application/gzip",
122
124
  ".gzip": "application/gzip",
123
125
  ".htm": "text/html",
@@ -129,6 +131,7 @@ FileExtensions: Final = {
129
131
  ".json": "application/json",
130
132
  ".jsonld": "application/ld+json",
131
133
  ".m4a": "audio/m4a",
134
+ ".md": "text/markdown",
132
135
  ".mkv": "video/x-matroska",
133
136
  ".mng": "image/x-mng",
134
137
  ".mov": "video/quicktime",
@@ -136,6 +139,7 @@ FileExtensions: Final = {
136
139
  ".mp4": "video/mp4",
137
140
  ".mpeg": "video/mpeg",
138
141
  ".mpg": "video/mpeg",
142
+ ".nc": "application/netcdf",
139
143
  ".odp": "application/vnd.oasis.opendocument.presentation",
140
144
  ".ods": "application/vnd.oasis.opendocument.spreadsheet",
141
145
  ".odt": "application/vnd.oasis.opendocument.text",
@@ -148,7 +152,6 @@ FileExtensions: Final = {
148
152
  ".png": "image/png",
149
153
  ".ppt": "application/vnd.ms-powerpoint",
150
154
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
151
- ".py": "text/x-python",
152
155
  ".sql": "application/sql",
153
156
  ".tar": "application/x-tar",
154
157
  ".tar.bz": "application/x-bzip",
@@ -157,6 +160,7 @@ FileExtensions: Final = {
157
160
  ".tgz": "application/gzip",
158
161
  ".tif": "image/tiff",
159
162
  ".tiff": "image/tiff",
163
+ ".toml": "application/toml",
160
164
  ".ttf": "font/ttf",
161
165
  ".txt": "text/plain",
162
166
  ".wav": "audio/wav",
@@ -174,141 +178,17 @@ FileExtensions: Final = {
174
178
  }
175
179
  FILE_EXTENSIONS = get_args(FileExtensions)
176
180
 
177
- MimeTypes = Literal[
178
- "application/epub+zip",
179
- "application/geo+json",
181
+ _MIME_TYPES_EXTRA: Final[tuple[str, ...]] = (
180
182
  "application/graphql",
181
- "application/gzip",
182
- "application/javascript",
183
- "application/json",
184
- "application/ld+json",
185
- "application/msword",
186
- "application/octet-stream",
187
- "application/pdf",
188
- "application/sql",
189
183
  "application/vnd.api+json",
190
- "application/vnd.ms-excel",
191
- "application/vnd.ms-powerpoint",
192
- "application/vnd.oasis.opendocument.presentation",
193
- "application/vnd.oasis.opendocument.spreadsheet",
194
- "application/vnd.oasis.opendocument.text",
195
- "application/vnd.openxmlformats-officedocument.presentationml.presentation",
196
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
197
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
198
- "application/x-7z-compressed",
199
- "application/x-bzip",
200
- "application/x-bzip2",
201
- "application/x-executable",
202
- "application/x-tar",
203
184
  "application/x-www-form-urlencoded",
204
185
  "application/xml",
205
- "application/zip",
206
- "application/zstd",
207
- "audio/aac",
208
- "audio/m4a",
209
- "audio/mpeg",
210
- "audio/ogg",
211
- "audio/wav",
212
- "audio/webm",
213
- "font/otf",
214
- "font/ttf",
215
- "image/apng",
216
- "image/bmp",
217
- "image/flif",
218
- "image/gif",
219
- "image/jpeg",
220
- "image/png",
221
- "image/tiff",
222
- "image/vnd.microsoft.icon",
223
- "image/webp",
224
- "image/x-mng",
225
186
  "multipart/form-data",
226
- "text/css",
227
- "text/csv",
228
- "text/html",
229
- "text/markdown",
230
- "text/php",
231
- "text/plain",
232
187
  "text/x-markdown",
233
- "text/x-yaml",
234
- "text/xml",
235
- "video/3gpp",
236
- "video/mp4",
237
- "video/mpeg",
238
- "video/ogg",
239
- "video/quicktime",
240
- "video/webm",
241
- "video/x-matroska",
242
- "video/x-ms-wmv",
243
- "video/x-msvideo",
244
- "application/epub+zip+crypt",
245
- "application/geo+json+crypt",
246
- "application/graphql+crypt",
247
- "application/gzip+crypt",
248
- "application/javascript+crypt",
249
- "application/json+crypt",
250
- "application/ld+json+crypt",
251
- "application/msword+crypt",
252
- "application/octet-stream+crypt",
253
- "application/pdf+crypt",
254
- "application/sql+crypt",
255
- "application/vnd.api+json+crypt",
256
- "application/vnd.ms-excel+crypt",
257
- "application/vnd.ms-powerpoint+crypt",
258
- "application/vnd.oasis.opendocument.presentation+crypt",
259
- "application/vnd.oasis.opendocument.spreadsheet+crypt",
260
- "application/vnd.oasis.opendocument.text+crypt",
261
- "application/vnd.openxmlformats-officedocument.presentationml.presentation+crypt",
262
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet+crypt",
263
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document+crypt",
264
- "application/x-7z-compressed+crypt",
265
- "application/x-bzip+crypt",
266
- "application/x-bzip2+crypt",
267
- "application/x-executable+crypt",
268
- "application/x-tar+crypt",
269
- "application/x-www-form-urlencoded+crypt",
270
- "application/xml+crypt",
271
- "application/zip+crypt",
272
- "application/zstd+crypt",
273
- "audio/aac+crypt",
274
- "audio/m4a+crypt",
275
- "audio/mpeg+crypt",
276
- "audio/ogg+crypt",
277
- "audio/wav+crypt",
278
- "audio/webm+crypt",
279
- "font/otf+crypt",
280
- "font/ttf+crypt",
281
- "image/apng+crypt",
282
- "image/bmp+crypt",
283
- "image/flif+crypt",
284
- "image/gif+crypt",
285
- "image/jpeg+crypt",
286
- "image/png+crypt",
287
- "image/tiff+crypt",
288
- "image/vnd.microsoft.icon+crypt",
289
- "image/webp+crypt",
290
- "image/x-mng+crypt",
291
- "multipart/form-data+crypt",
292
- "text/css+crypt",
293
- "text/csv+crypt",
294
- "text/html+crypt",
295
- "text/markdown+crypt",
296
- "text/php+crypt",
297
- "text/plain+crypt",
298
- "text/x-markdown+crypt",
299
- "text/x-yaml+crypt",
300
- "text/xml+crypt",
301
- "video/3gpp+crypt",
302
- "video/mp4+crypt",
303
- "video/mpeg+crypt",
304
- "video/ogg+crypt",
305
- "video/quicktime+crypt",
306
- "video/webm+crypt",
307
- "video/x-matroska+crypt",
308
- "video/x-ms-wmv+crypt",
309
- "video/x-msvideo+crypt",
310
- ]
311
- MIME_TYPES = get_args(MimeTypes)
188
+ )
189
+
190
+ _MIME_TYPES_BASE: Final[tuple[str, ...]] = tuple(sorted(set(FileExtensions.values()) | set(_MIME_TYPES_EXTRA)))
191
+ MIME_TYPES: Final[tuple[str, ...]] = _MIME_TYPES_BASE + tuple(f"{m}+crypt" for m in _MIME_TYPES_BASE)
312
192
 
313
193
  MimeTypesUtf8Text = Literal[
314
194
  "application/geo+json",