rudi-node-write 1.1.1__tar.gz → 1.2.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 (44) hide show
  1. {rudi_node_write-1.1.1/src/rudi_node_write.egg-info → rudi_node_write-1.2.0}/PKG-INFO +1 -1
  2. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/pyproject.toml +2 -2
  3. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_connector.py +6 -5
  4. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_api_write.py +16 -8
  5. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_jwt_factory.py +6 -2
  6. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_manager_write.py +67 -32
  7. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_media_write.py +8 -3
  8. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_node_writer.py +18 -2
  9. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/serializable.py +3 -3
  10. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/err.py +14 -11
  11. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/file_utils.py +1 -0
  12. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/str_utils.py +9 -0
  13. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/url_utils.py +9 -0
  14. rudi_node_write-1.2.0/src/rudi_node_write/wip/federation_backup.py +145 -0
  15. rudi_node_write-1.2.0/src/rudi_node_write/wip/rudinode_federation.py +412 -0
  16. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0/src/rudi_node_write.egg-info}/PKG-INFO +1 -1
  17. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write.egg-info/SOURCES.txt +2 -0
  18. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/LICENCE.md +0 -0
  19. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/README.md +0 -0
  20. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/requirements.txt +0 -0
  21. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/setup.cfg +0 -0
  22. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/__init__.py +0 -0
  23. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/conf/meta_defaults.py +0 -0
  24. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/rudi_node_auth.py +0 -0
  25. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_const.py +0 -0
  26. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_contact.py +0 -0
  27. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_dates.py +0 -0
  28. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_dictionary_entry.py +0 -0
  29. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_geo.py +0 -0
  30. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_licence.py +0 -0
  31. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_media.py +0 -0
  32. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_meta.py +0 -0
  33. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_meta_misc.py +0 -0
  34. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_org.py +0 -0
  35. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/dict_utils.py +0 -0
  36. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/html_utils.py +0 -0
  37. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/jwt.py +0 -0
  38. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/list_utils.py +0 -0
  39. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/log.py +0 -0
  40. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/type_date.py +0 -0
  41. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/typing_utils.py +0 -0
  42. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write.egg-info/dependency_links.txt +0 -0
  43. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write.egg-info/top_level.txt +0 -0
  44. {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/tests/test_rudi_node_write.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rudi-node-write
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Use the internal API of a RUDI Producer node
5
5
  Author-email: Olivier Martineau <olivier.martineau@irisa.fr>
6
6
  Maintainer-email: Olivier Martineau <olivier.martineau@irisa.fr>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rudi-node-write"
7
- version = "1.1.1"
7
+ version = "1.2.0"
8
8
  authors = [{ name = "Olivier Martineau", email = "olivier.martineau@irisa.fr" }]
9
9
  maintainers = [{ name = "Olivier Martineau", email = "olivier.martineau@irisa.fr" }]
10
10
  description = "Use the internal API of a RUDI Producer node"
@@ -34,7 +34,7 @@ target-version = ['py311']
34
34
  # ----- Tool: commitizen
35
35
  [tool.commitizen]
36
36
  name = "cz_conventional_commits"
37
- version = "1.1.1"
37
+ version = "1.2.0"
38
38
  version_files = ["pyproject.toml:version"]
39
39
 
40
40
  # ----- Tool: pytest
@@ -44,12 +44,13 @@ def https_download(resource_url: str, headers=None, should_show_debug_line: bool
44
44
  class Connector:
45
45
  _default_connector = None
46
46
 
47
- def __init__(self, server_url: str):
47
+ def __init__(self, server_url: str, keep_connection: bool = False):
48
48
  self.scheme = None
49
49
  self.host = None
50
50
  self.path = None
51
51
  self.base_url = None
52
52
  self.connection = None
53
+ self.keep_connection = keep_connection
53
54
 
54
55
  self._set_url(server_url)
55
56
  self._cookies = None
@@ -63,7 +64,7 @@ class Connector:
63
64
  self.host = netloc
64
65
  self.path = path
65
66
  self.base_url = slash_join(f"{self.scheme}://{self.host}", self.path)
66
- log_d(here, "base_url", self.base_url)
67
+ # log_d(here, "base_url", self.base_url)
67
68
 
68
69
  @property
69
70
  def class_name(self):
@@ -119,7 +120,7 @@ class Connector:
119
120
  else:
120
121
  log_d_if(should_log_response, here, f"OK {response.status}", path_url)
121
122
  res_data = response.read()
122
- if not keep_alive:
123
+ if not keep_alive and not self.keep_connection:
123
124
  self.connection.close()
124
125
  if not res_data:
125
126
  log_e(here, "empty data?")
@@ -214,7 +215,7 @@ class Connector:
214
215
  except (TypeError, JSONDecodeError):
215
216
  response_data = repr(rdata)
216
217
  log_d_if(should_log_response, here, "Response is not a JSON", response_data)
217
- if not keep_alive:
218
+ if not keep_alive and not self.keep_connection:
218
219
  self.close_connection()
219
220
 
220
221
  if type(response_data) is str:
@@ -226,7 +227,7 @@ class Connector:
226
227
  if response.status >= 400:
227
228
  log_e(here, "Connection error", response_data)
228
229
  log_e(here, "Request in error", req_method, self.full_url(relative_url))
229
- log_e(here, "Headers", response.headers)
230
+ # log_e(here, "Headers", response.headers)
230
231
  raise HttpError(response_data, req_method, self.base_url, relative_url)
231
232
 
232
233
 
@@ -30,6 +30,8 @@ from rudi_node_write.utils.list_utils import get_first_list_elt_or_none
30
30
  from rudi_node_write.utils.log import log_d, log_e, log_w
31
31
  from rudi_node_write.utils.str_utils import slash_join, uuid4_str, is_uuid_v4
32
32
  from rudi_node_write.utils.typing_utils import get_type_name, check_type
33
+ from rudi_node_write.utils.url_utils import ensure_url_startswith
34
+
33
35
 
34
36
  REQ_LIMIT = 500
35
37
  _DELAY_REFRESH_S = 60 # seconds
@@ -39,11 +41,13 @@ _STATUS_SKIPPED = "skipped"
39
41
  _STATUS_MISSING = "missing"
40
42
  _STATUS_DOWNLOADED = "downloaded"
41
43
 
42
- _API_ADMIN_PATH = "api/admin"
43
-
44
44
  here = "RudiNodeApiConnector"
45
45
 
46
46
 
47
+ def ensure_url_startswith_api_admin(url):
48
+ return ensure_url_startswith(url, "api/admin")
49
+
50
+
47
51
  class RudiNodeApiConnector(Connector):
48
52
  def __init__(
49
53
  self,
@@ -84,7 +88,7 @@ class RudiNodeApiConnector(Connector):
84
88
  log_e(here, f"!! Node '{self.host}'", "no connection!")
85
89
  raise ConnectionError(f"An error occurred while connecting to RUDI node JWT server {self.base_url}")
86
90
 
87
- log_d(here, f"Node '{self.host}'", "connection OK")
91
+ # log_d(here, f"Node '{self.host}'", "connection OK")
88
92
  return test
89
93
 
90
94
  def set_jwt_factory(self, jwt_factory: RudiNodeJwtFactory) -> None:
@@ -112,7 +116,9 @@ class RudiNodeApiConnector(Connector):
112
116
  def _headers(self):
113
117
  return self._initial_headers | {"Authorization": f"Bearer {self._jwt}"}
114
118
 
115
- def get_admin_api(self, url: str, keep_alive: bool = False) -> str | int | list | dict:
119
+ def get_admin_api(
120
+ self, url: str, keep_alive: bool = False, should_log_request=False, should_log_response=False
121
+ ) -> str | int | list | dict:
116
122
  """
117
123
  Performs an identified GET request through /api/admin path
118
124
  :param url: part of the URL that comes after /api/admin
@@ -122,9 +128,11 @@ class RudiNodeApiConnector(Connector):
122
128
  """
123
129
  return self.request(
124
130
  req_method="GET",
125
- relative_url=slash_join(_API_ADMIN_PATH, url),
131
+ relative_url=ensure_url_startswith_api_admin(url),
126
132
  headers=self._headers,
127
133
  keep_alive=keep_alive,
134
+ should_log_request=should_log_request,
135
+ should_log_response=should_log_response,
128
136
  ) # type: ignore
129
137
 
130
138
  def put_admin_api(
@@ -138,7 +146,7 @@ class RudiNodeApiConnector(Connector):
138
146
  """
139
147
  return self.request(
140
148
  req_method="PUT",
141
- relative_url=slash_join(_API_ADMIN_PATH, url),
149
+ relative_url=ensure_url_startswith_api_admin(url),
142
150
  headers=headers if headers else self._headers,
143
151
  body=payload,
144
152
  keep_alive=keep_alive,
@@ -155,7 +163,7 @@ class RudiNodeApiConnector(Connector):
155
163
  """
156
164
  return self.request(
157
165
  req_method="POST",
158
- relative_url=slash_join(_API_ADMIN_PATH, url),
166
+ relative_url=ensure_url_startswith_api_admin(url),
159
167
  headers=headers if headers else self._headers,
160
168
  body=payload,
161
169
  keep_alive=keep_alive,
@@ -170,7 +178,7 @@ class RudiNodeApiConnector(Connector):
170
178
  """
171
179
  return self.request(
172
180
  req_method="DELETE",
173
- relative_url=slash_join(_API_ADMIN_PATH, url),
181
+ relative_url=ensure_url_startswith_api_admin(url),
174
182
  headers=headers if headers else self._headers,
175
183
  keep_alive=keep_alive,
176
184
  )
@@ -22,10 +22,14 @@ class RudiNodeJwtFactory(Connector):
22
22
  headers_user_agent: str = "RudiNodeJwtFactory",
23
23
  ):
24
24
  super().__init__(server_url)
25
+ self.sub = "rudi_prod_token"
25
26
  if has_key(auth, B64_AUTH_KEY):
26
27
  self._b64_auth = f"Basic {auth[B64_AUTH_KEY]}"
27
28
  elif has_key(auth, USR_AUTH_KEY) and has_key(auth, PWD_AUTH_KEY):
28
29
  self._b64_auth = get_basic_auth(auth[USR_AUTH_KEY], auth[PWD_AUTH_KEY])
30
+ elif has_key(auth, "sub"):
31
+ self.sub = auth["sub"]
32
+ self._b64_auth = ""
29
33
  else:
30
34
  err_msg = f"{B64_AUTH_KEY}', or both '{USR_AUTH_KEY}' and '{PWD_AUTH_KEY}"
31
35
  raise UnexpectedValueException("auth", err_msg, auth)
@@ -45,7 +49,7 @@ class RudiNodeJwtFactory(Connector):
45
49
  if not isinstance(test, dict) or test["RUDI"] != "JWT":
46
50
  log_e("RudiNodeJwtFactory", f"!! Node '{self.host}'", "no connection!")
47
51
  raise ConnectionError(f"An error occurred while connecting to RUDI node JWT server {self.base_url}")
48
- log_d("RudiNodeJwtFactory", f"Node '{self.host}'", "connection OK")
52
+ # log_d("RudiNodeJwtFactory", f"Node '{self.host}'", "connection OK")
49
53
  return True
50
54
 
51
55
  def _renew_jwt(self, delay_s: int) -> str:
@@ -53,7 +57,7 @@ class RudiNodeJwtFactory(Connector):
53
57
  delay_s = self._default_exp_s
54
58
  jwt_body = {
55
59
  "exp": Date.time_epoch_s(delay_s),
56
- "sub": "rudi_prod_token",
60
+ "sub": self.sub,
57
61
  "req_mtd": "all",
58
62
  "req_url": "all",
59
63
  }
@@ -27,6 +27,7 @@ from rudi_node_write.utils.log import log_d, log_e, log_w
27
27
  from rudi_node_write.utils.str_utils import check_is_uuid4, is_uuid_v4, slash_join, uuid4_str
28
28
  from rudi_node_write.utils.type_date import Date
29
29
  from rudi_node_write.utils.typing_utils import check_is_int, get_type_name
30
+ from rudi_node_write.utils.url_utils import ensure_url_startswith
30
31
 
31
32
 
32
33
  # Defaults for constructor
@@ -46,12 +47,17 @@ _STATUS_SKIPPED = "skipped"
46
47
  _STATUS_MISSING = "missing"
47
48
  _STATUS_DOWNLOADED = "downloaded"
48
49
 
49
- # Path constant
50
- API_DATA = "api/data"
51
-
52
50
  here = "RudiNodeManagerConnector"
53
51
 
54
52
 
53
+ def ensure_url_startswith_api(url):
54
+ return ensure_url_startswith(url, "api")
55
+
56
+
57
+ def ensure_url_startswith_api_data(url):
58
+ return ensure_url_startswith(url, "api/data")
59
+
60
+
55
61
  class RudiNodeManagerConnector(Connector):
56
62
  """
57
63
  Every RUDI node has a UI module called "RUDI node manager", or "producer node manager" (shortened as "prod-manager")
@@ -64,9 +70,10 @@ class RudiNodeManagerConnector(Connector):
64
70
  server_url: str,
65
71
  auth: RudiNodeAuth | dict,
66
72
  headers_user_agent: str = _DEFAULT_USER_AGENT,
73
+ keep_connection: bool = False,
67
74
  ):
68
75
  self.server_url = server_url
69
- super().__init__(server_url)
76
+ super().__init__(server_url=server_url, keep_connection=keep_connection)
70
77
 
71
78
  if isinstance(auth, RudiNodeAuth):
72
79
  self._auth = auth
@@ -76,6 +83,8 @@ class RudiNodeManagerConnector(Connector):
76
83
  raise TypeError("Input 'auth' parameter should either be a 'RudiNodeAuth' object or a dict.")
77
84
  self._headers_user_agent = headers_user_agent
78
85
 
86
+ self._is_legacy = False
87
+
79
88
  self._cached_pm_jwt = ""
80
89
  self._cached_pm_headers = None
81
90
 
@@ -87,7 +96,7 @@ class RudiNodeManagerConnector(Connector):
87
96
 
88
97
  self._data_cache = {}
89
98
 
90
- self.test_identified_connection()
99
+ # self.test_identified_connection()
91
100
 
92
101
  @property
93
102
  def _usr(self):
@@ -108,7 +117,7 @@ class RudiNodeManagerConnector(Connector):
108
117
  """
109
118
  return self.request(
110
119
  req_method="GET",
111
- relative_url=slash_join("api", url),
120
+ relative_url=ensure_url_startswith_api(url),
112
121
  headers=headers,
113
122
  keep_alive=keep_alive,
114
123
  should_log_request=should_log_request,
@@ -123,7 +132,7 @@ class RudiNodeManagerConnector(Connector):
123
132
  """
124
133
  return self.request(
125
134
  req_method="PUT",
126
- relative_url=slash_join("api", url),
135
+ relative_url=ensure_url_startswith_api(url),
127
136
  headers=headers,
128
137
  body=body,
129
138
  keep_alive=keep_alive,
@@ -139,7 +148,7 @@ class RudiNodeManagerConnector(Connector):
139
148
  """
140
149
  return self.request(
141
150
  req_method="POST",
142
- relative_url=slash_join("api", url),
151
+ relative_url=ensure_url_startswith_api(url),
143
152
  headers=headers,
144
153
  body=body,
145
154
  keep_alive=keep_alive,
@@ -155,7 +164,7 @@ class RudiNodeManagerConnector(Connector):
155
164
  """
156
165
  return self.request(
157
166
  req_method="DELETE",
158
- relative_url=slash_join("api", url),
167
+ relative_url=ensure_url_startswith_api(url),
159
168
  headers=headers,
160
169
  keep_alive=keep_alive,
161
170
  should_log_request=should_log_request,
@@ -172,7 +181,7 @@ class RudiNodeManagerConnector(Connector):
172
181
  def test_identified_connection(self):
173
182
  self.test_connection()
174
183
  try:
175
- self.init_data
184
+ self._get_api(url="data/uuid", headers=self._pm_headers)
176
185
  except HttpError:
177
186
  raise ConnectionError(f"Identifiers seem to be not working for the node: {self.server_url}")
178
187
 
@@ -228,10 +237,15 @@ class RudiNodeManagerConnector(Connector):
228
237
 
229
238
  @property
230
239
  def node_urls(self):
240
+ if self._is_legacy:
241
+ log_w(here, "The server does not know the URL: front/node-urls")
242
+ return
231
243
  try:
232
244
  return self._get_cache("front/node-urls")
233
245
  except:
234
246
  log_w(here, "The server does not know the URL: front/node-urls")
247
+ self._is_legacy = True
248
+ return None
235
249
 
236
250
  @property
237
251
  def media_url(self) -> str:
@@ -243,7 +257,7 @@ class RudiNodeManagerConnector(Connector):
243
257
  self._cached_media_url = media_url
244
258
  return self._cached_media_url
245
259
  self.set_media_url(slash_join(f"{self.scheme}://{self.host}", "media"))
246
- log_d(here, "_cached_media_url:", self._cached_media_url)
260
+ # log_d(here, "_cached_media_url:", self._cached_media_url)
247
261
  return self._cached_media_url
248
262
 
249
263
  @property
@@ -255,7 +269,7 @@ class RudiNodeManagerConnector(Connector):
255
269
  if not isinstance(res, dict):
256
270
  raise TypeError(f"An error occurred while getting Media JWT: got {res}")
257
271
  jwt = f'{res.get("token")}'
258
- log_d(here, "jwt:", jwt)
272
+ # log_d(here, "jwt:", jwt)
259
273
  if is_jwt_expired(jwt):
260
274
  raise HttpErrorNotFound(f"Could not access a valid JWT, got: {jwt}")
261
275
  self._cached_media_jwt = jwt
@@ -281,7 +295,7 @@ class RudiNodeManagerConnector(Connector):
281
295
  """
282
296
  return self.request(
283
297
  req_method="GET",
284
- relative_url=slash_join(API_DATA, url),
298
+ relative_url=ensure_url_startswith_api_data(url),
285
299
  headers=self._pm_headers,
286
300
  keep_alive=keep_alive,
287
301
  )
@@ -297,7 +311,7 @@ class RudiNodeManagerConnector(Connector):
297
311
  self._force_clean_cache()
298
312
  return self.request(
299
313
  req_method="PUT",
300
- relative_url=slash_join(API_DATA, url),
314
+ relative_url=ensure_url_startswith_api_data(url),
301
315
  headers=self._pm_headers,
302
316
  body=body,
303
317
  keep_alive=keep_alive,
@@ -315,7 +329,7 @@ class RudiNodeManagerConnector(Connector):
315
329
  self._force_clean_cache()
316
330
  return self.request(
317
331
  req_method="DELETE",
318
- relative_url=slash_join(API_DATA, url),
332
+ relative_url=ensure_url_startswith_api_data(url),
319
333
  headers=self._pm_headers,
320
334
  keep_alive=keep_alive,
321
335
  )
@@ -371,8 +385,14 @@ class RudiNodeManagerConnector(Connector):
371
385
  :param obj_type: one of RUDI object types
372
386
  :return: the list of objects for this type
373
387
  """
388
+ here = f"{self.class_name}.get_data"
389
+
374
390
  if obj_type in RUDI_OBJECT_TYPES:
375
- return self._get_cache(obj_type)
391
+ if not (self._is_legacy and obj_type == "media"):
392
+ return self._get_cache(obj_type)
393
+ else:
394
+ log_w(here, "Legacy node, request not available for 'media'")
395
+ return {}
376
396
  else:
377
397
  return self._get_cache(slash_join("data", obj_type))
378
398
 
@@ -460,10 +480,14 @@ class RudiNodeManagerConnector(Connector):
460
480
  :return: the list of the media declared on the RUDI producer node
461
481
  """
462
482
  fun = here + ".media_list"
463
- try:
464
- list_medias = self.get_data("media")
465
- except:
466
- log_w(fun, "Cannot acces /data/media on pm")
483
+ list_medias = None
484
+ if not self._is_legacy:
485
+ try:
486
+ list_medias = self.get_data("media")
487
+ except:
488
+ log_w(fun, f"Cannot acces /data/media on node {self.base_url}")
489
+ self._is_legacy = True
490
+
467
491
  if not isinstance(list_medias, list) or len(list_medias) == 0:
468
492
  medias = {}
469
493
  metadatas = self.metadata_list
@@ -1093,7 +1117,16 @@ class RudiNodeManagerConnector(Connector):
1093
1117
  # log_d(here, "media_info", str(media_info))
1094
1118
 
1095
1119
  # Posting the file metadata as a RudiMediaFile object to the RUDI API module through the PM data API
1096
- api_media_info = self.put_api_data("media", media_info)
1120
+ if not self._is_legacy:
1121
+ try:
1122
+ api_media_info = self.put_api_data("media", media_info)
1123
+ except HttpError:
1124
+ self._is_legacy = True
1125
+ log_w(
1126
+ here,
1127
+ "The Manager module for this node cannot commit RudiMediaFile on the Catalog, please handle this by updating the file storage status in the metadata",
1128
+ )
1129
+ return media_info
1097
1130
 
1098
1131
  # Committing the file that has been uploaded
1099
1132
  log_d(here, "zone_name", zone_name)
@@ -1308,15 +1341,17 @@ if __name__ == "__main__": # pragma: no cover
1308
1341
  text_file = "unicode_chars.txt"
1309
1342
  yaml_file = "RUDI producer internal API - 1.3.0.yml"
1310
1343
  for i, f in enumerate([text_file, yaml_file]):
1311
- upload_res = pm_connector.post_local_file(slash_join(dwnld_dir, f), media_uuid[i])
1312
- log_d(tests, f"File '{f}' now available at", upload_res)
1313
- stored_media_list = [
1314
- stored_media for stored_media in rudi_media.media_list if stored_media.get("uuid") == media_uuid[i]
1315
- ]
1316
- log_d(
1317
- tests,
1318
- "stored_media",
1319
- stored_media_list[0] if len(stored_media_list) > 0 else None,
1320
- )
1321
-
1344
+ try:
1345
+ upload_res = pm_connector.post_local_file(slash_join(dwnld_dir, f), media_uuid[i])
1346
+ log_d(tests, f"File '{f}' now available at", upload_res)
1347
+ stored_media_list = [
1348
+ stored_media for stored_media in rudi_media.media_list if stored_media.get("uuid") == media_uuid[i]
1349
+ ]
1350
+ log_d(
1351
+ tests,
1352
+ "stored_media",
1353
+ stored_media_list[0] if len(stored_media_list) > 0 else None,
1354
+ )
1355
+ except HttpError as e:
1356
+ log_w(tests, e)
1322
1357
  log_d(tests, "exec. time", time() - begin)
@@ -12,10 +12,15 @@ from rudi_node_write.utils.file_utils import read_json_file, FileDetails
12
12
  from rudi_node_write.utils.jwt import get_basic_auth
13
13
  from rudi_node_write.utils.log import log_d, log_w
14
14
  from rudi_node_write.utils.str_utils import slash_join, uuid4_str
15
+ from rudi_node_write.utils.url_utils import ensure_url_startswith
15
16
 
16
17
  MAX_SIZE = 524288000 # (== 500 MB)
17
18
 
18
19
 
20
+ def ensure_url_startswith_media(url):
21
+ return ensure_url_startswith(url, "media")
22
+
23
+
19
24
  class FileTooBigException(Exception):
20
25
  def __init__(self, file_size):
21
26
  super().__init__(
@@ -129,7 +134,7 @@ class RudiNodeMediaConnector(Connector):
129
134
  headers: dict | None = None,
130
135
  ):
131
136
  return self.request(
132
- relative_url=slash_join("media", relative_url),
137
+ relative_url=ensure_url_startswith_media(relative_url),
133
138
  headers=headers if headers is not None else self._get_headers(),
134
139
  req_method="GET",
135
140
  )
@@ -141,7 +146,7 @@ class RudiNodeMediaConnector(Connector):
141
146
  headers: dict | None = None,
142
147
  ):
143
148
  return self.request(
144
- relative_url=slash_join("media", relative_url),
149
+ relative_url=ensure_url_startswith_media(relative_url),
145
150
  headers=headers if headers is not None else self._get_headers(),
146
151
  body=payload,
147
152
  req_method="POST",
@@ -149,7 +154,7 @@ class RudiNodeMediaConnector(Connector):
149
154
 
150
155
  def _put_api_media(self, relative_url: str, payload: str | dict | BinaryIO, headers: dict | None = None):
151
156
  return self.request(
152
- relative_url=slash_join("media", relative_url),
157
+ relative_url=ensure_url_startswith_media(relative_url),
153
158
  headers=headers if headers is not None else self._get_headers(),
154
159
  body=payload,
155
160
  req_method="PUT",
@@ -28,7 +28,13 @@ _STATUS_DOWNLOADED = "downloaded"
28
28
  class RudiNodeWriter:
29
29
  _default_getter = None
30
30
 
31
- def __init__(self, pm_url: str, auth: RudiNodeAuth, headers_user_agent: str = _USER_AGENT_DEFAULT):
31
+ def __init__(
32
+ self,
33
+ pm_url: str,
34
+ auth: RudiNodeAuth,
35
+ headers_user_agent: str = _USER_AGENT_DEFAULT,
36
+ keep_connection: bool = False,
37
+ ):
32
38
  """
33
39
  The main object of this library.
34
40
  :param pm_url: the URL of the RUDI node
@@ -39,20 +45,30 @@ class RudiNodeWriter:
39
45
  self._pm_url = pm_url
40
46
  self._auth = auth
41
47
  self._headers_user_agent = headers_user_agent
48
+ self.keep_connection = keep_connection
49
+
42
50
  self._init_pm_connector()
43
- self.connector.test_identified_connection()
51
+ # self.connector.test_identified_connection()
44
52
 
45
53
  def _init_pm_connector(self) -> None:
46
54
  self._pm_connector = RudiNodeManagerConnector(
47
55
  server_url=self._pm_url,
48
56
  auth=self._auth,
49
57
  headers_user_agent=self._headers_user_agent,
58
+ keep_connection=self.keep_connection,
50
59
  )
51
60
 
61
+ def close_connection(self):
62
+ self._pm_connector.close_connection()
63
+
52
64
  @property
53
65
  def headers_user_agent(self) -> str:
54
66
  return self._headers_user_agent
55
67
 
68
+ @property
69
+ def is_legacy(self) -> str:
70
+ return self._pm_connector._is_legacy
71
+
56
72
  @property
57
73
  def connector(self) -> RudiNodeManagerConnector:
58
74
  """
@@ -24,7 +24,7 @@ class Serializable(ABC):
24
24
  def class_name(self):
25
25
  return self.__class__.__name__
26
26
 
27
- def __eq__(self, other):
27
+ def __eq__(self, other): # NOSONAR
28
28
  here = f"{self.class_name}._eq_"
29
29
  if other is None:
30
30
  log_d(here, f"Target is null. {self} ≠ {other}")
@@ -36,6 +36,8 @@ class Serializable(ABC):
36
36
  other_json = other.to_json()
37
37
  if not isinstance(self_json, type(other_json)):
38
38
  return False
39
+ if isinstance(self_json, list) and isinstance(other_json, list):
40
+ return sorted(self_json) == sorted(other_json)
39
41
  if isinstance(self_json, dict) and isinstance(other_json, dict):
40
42
  for key in self_json.keys():
41
43
  if (val_b := other_json.get(key)) is None:
@@ -50,8 +52,6 @@ class Serializable(ABC):
50
52
  return True
51
53
  log_d(here, f"target still has some unmatched keys: {other_json}")
52
54
  return False
53
- elif isinstance(self_json, list) and isinstance(other_json, list):
54
- return sorted(self_json) == sorted(other_json)
55
55
  return self_json == other_json
56
56
 
57
57
  def __ne__(self, other):
@@ -57,25 +57,28 @@ class ExpiredTokenException(Exception):
57
57
  super().__init__(f"JWT has expired: {exp} < {now_epoch_s}")
58
58
 
59
59
 
60
- def rudi_api_http_error_to_string(status, err_type, err_msg):
61
- return f"HTTP ERR {status} {err_type}: {err_msg}"
62
-
63
-
64
60
  class HttpError(Exception):
65
61
  status = 500
66
62
 
67
63
  def __init__(self, err, req_method: str | None = None, base_url: str | None = None, url: str | None = None):
68
64
  here = "HttpError"
69
- err_msg = f"HTTP ERR {self.status} {err}"
65
+ self.method = req_method
66
+ self.base_url = base_url
67
+ self.url = url
70
68
  # print(here, f"http err {self.status}:", err)
71
69
  if type(err) is dict and "error" in err and "message" in err:
72
- if "status" in err:
73
- err_msg = rudi_api_http_error_to_string(err["status"], err["error"], err["message"])
74
- elif "statusCode" in err:
75
- err_msg = rudi_api_http_error_to_string(err["statusCode"], err["error"], err["message"])
70
+ err_status = err["status"] if "status" in err else err.get("statusCode")
71
+ err_type = err["error"]
72
+ err_msg = err["message"]
73
+ self.message = f"{err_status} {err_type}: {err_msg}"
74
+ else:
75
+ self.message = f"{self.status} {err}"
76
76
  if req_method and base_url:
77
- err_msg = f"HTTP ERR for request '{req_method} {slash_join(base_url, url)}' -> {err}"
78
- super().__init__(err_msg)
77
+ self.message = f"for request '{req_method} {slash_join(base_url, url)}' -> {err}"
78
+ super().__init__(self.message)
79
+
80
+ def __str__(self):
81
+ return f"HTTP ERR {self.message}"
79
82
 
80
83
 
81
84
  class HttpErrorNotFound(HttpError):
@@ -115,6 +115,7 @@ def get_file_hash(file_local_path: str, hash_algo: str = "md5") -> str:
115
115
 
116
116
 
117
117
  def read_json_file(file_path, mode: Literal["b", "t"] = "t"): # pragma: no cover
118
+ check_is_file(file_path)
118
119
  with open(file_path, f"r{mode}") as json_file_content:
119
120
  json_dict = load(json_file_content)
120
121
  return json_dict
@@ -1,4 +1,5 @@
1
1
  from re import compile
2
+ from typing import Callable
2
3
  from uuid import UUID, uuid4
3
4
 
4
5
  from rudi_node_write.utils.typing_utils import get_type_name
@@ -81,3 +82,11 @@ def slash_join(*args):
81
82
  non_null_args.append(frag.strip("/"))
82
83
  joined_str = "/".join(non_null_args)
83
84
  return joined_str
85
+
86
+
87
+ def ensure_startswith(s: str, test_str: str, start_str: str | None = None, transform: Callable | None = None):
88
+ if s.startswith(test_str):
89
+ return s
90
+ if transform is None:
91
+ return test_str + s if start_str is None else start_str + s
92
+ return transform(s)
@@ -2,9 +2,18 @@ from http.client import HTTPResponse
2
2
  from urllib.parse import quote
3
3
 
4
4
  from rudi_node_write.utils.log import log_d
5
+ from rudi_node_write.utils.str_utils import slash_join
5
6
  from rudi_node_write.utils.typing_utils import get_type_name
6
7
 
7
8
 
9
+ def ensure_http(url: str, scheme="https://"):
10
+ return url if url.startswith("http") else slash_join(scheme, "/", url)
11
+
12
+
13
+ def ensure_url_startswith(url: str, str_start: str):
14
+ return url if url.startswith(str_start) else slash_join(str_start, url)
15
+
16
+
8
17
  def get_response_cookies(http_response: HTTPResponse):
9
18
  cookie_list = http_response.headers.get_all("set-cookie")
10
19
  # log_d("get_response_cookies", "cookie_list", cookie_list)