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.
- {rudi_node_write-1.1.1/src/rudi_node_write.egg-info → rudi_node_write-1.2.0}/PKG-INFO +1 -1
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/pyproject.toml +2 -2
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_connector.py +6 -5
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_api_write.py +16 -8
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_jwt_factory.py +6 -2
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_manager_write.py +67 -32
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_media_write.py +8 -3
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_node_writer.py +18 -2
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/serializable.py +3 -3
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/err.py +14 -11
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/file_utils.py +1 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/str_utils.py +9 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/url_utils.py +9 -0
- rudi_node_write-1.2.0/src/rudi_node_write/wip/federation_backup.py +145 -0
- rudi_node_write-1.2.0/src/rudi_node_write/wip/rudinode_federation.py +412 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0/src/rudi_node_write.egg-info}/PKG-INFO +1 -1
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write.egg-info/SOURCES.txt +2 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/LICENCE.md +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/README.md +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/requirements.txt +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/setup.cfg +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/__init__.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/conf/meta_defaults.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/rudi_node_auth.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_const.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_contact.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_dates.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_dictionary_entry.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_geo.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_licence.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_media.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_meta.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_meta_misc.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/rudi_org.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/dict_utils.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/html_utils.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/jwt.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/list_utils.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/log.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/type_date.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/utils/typing_utils.py +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write.egg-info/dependency_links.txt +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write.egg-info/top_level.txt +0 -0
- {rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/tests/test_rudi_node_write.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rudi-node-write"
|
|
7
|
-
version = "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.
|
|
37
|
+
version = "1.2.0"
|
|
38
38
|
version_files = ["pyproject.toml:version"]
|
|
39
39
|
|
|
40
40
|
# ----- Tool: pytest
|
{rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_connector.py
RENAMED
|
@@ -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
|
|
{rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/connectors/io_rudi_api_write.py
RENAMED
|
@@ -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(
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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":
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
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__(
|
|
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
|
"""
|
{rudi_node_write-1.1.1 → rudi_node_write-1.2.0}/src/rudi_node_write/rudi_types/serializable.py
RENAMED
|
@@ -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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
super().__init__(
|
|
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)
|