digitalhub 0.14.0b5__py3-none-any.whl → 0.14.0b7__py3-none-any.whl

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.

Potentially problematic release.


This version of digitalhub might be problematic. Click here for more details.

Files changed (41) hide show
  1. digitalhub/__init__.py +1 -1
  2. digitalhub/context/api.py +1 -5
  3. digitalhub/context/builder.py +1 -1
  4. digitalhub/entities/_base/material/utils.py +0 -4
  5. digitalhub/entities/_commons/enums.py +1 -0
  6. digitalhub/entities/_commons/utils.py +19 -0
  7. digitalhub/entities/_processors/base/crud.py +1 -1
  8. digitalhub/entities/_processors/base/import_export.py +3 -2
  9. digitalhub/entities/_processors/base/processor.py +3 -3
  10. digitalhub/entities/_processors/context/crud.py +22 -24
  11. digitalhub/entities/_processors/context/import_export.py +2 -2
  12. digitalhub/entities/_processors/context/special_ops.py +10 -10
  13. digitalhub/entities/_processors/utils.py +5 -5
  14. digitalhub/entities/artifact/utils.py +2 -2
  15. digitalhub/entities/dataitem/utils.py +10 -14
  16. digitalhub/entities/model/utils.py +2 -2
  17. digitalhub/entities/project/_base/entity.py +248 -102
  18. digitalhub/entities/task/_base/models.py +13 -16
  19. digitalhub/stores/client/_base/key_builder.py +1 -1
  20. digitalhub/stores/client/builder.py +1 -1
  21. digitalhub/stores/client/dhcore/client.py +19 -303
  22. digitalhub/stores/client/dhcore/configurator.py +1 -1
  23. digitalhub/stores/client/dhcore/header_manager.py +61 -0
  24. digitalhub/stores/client/dhcore/http_handler.py +133 -0
  25. digitalhub/stores/client/dhcore/response_processor.py +102 -0
  26. digitalhub/stores/client/dhcore/utils.py +2 -60
  27. digitalhub/stores/client/local/client.py +2 -2
  28. digitalhub/stores/credentials/api.py +0 -4
  29. digitalhub/stores/credentials/ini_module.py +0 -6
  30. digitalhub/stores/data/builder.py +1 -1
  31. digitalhub/stores/data/s3/store.py +1 -1
  32. digitalhub/stores/data/sql/store.py +6 -6
  33. digitalhub/utils/generic_utils.py +0 -12
  34. digitalhub/utils/git_utils.py +0 -8
  35. digitalhub/utils/io_utils.py +0 -8
  36. digitalhub/utils/store_utils.py +1 -1
  37. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b7.dist-info}/METADATA +1 -1
  38. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b7.dist-info}/RECORD +41 -38
  39. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b7.dist-info}/WHEEL +0 -0
  40. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b7.dist-info}/licenses/AUTHORS +0 -0
  41. {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.0b7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,133 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ from requests import request
8
+
9
+ from digitalhub.stores.client.dhcore.configurator import ClientDHCoreConfigurator
10
+ from digitalhub.stores.client.dhcore.response_processor import ResponseProcessor
11
+ from digitalhub.utils.exceptions import BackendError
12
+
13
+ # Default timeout for requests (in seconds)
14
+ DEFAULT_TIMEOUT = 60
15
+
16
+
17
+ class HttpRequestHandler:
18
+ """
19
+ Handles HTTP request execution for DHCore client.
20
+
21
+ Encapsulates all HTTP communication logic including request execution,
22
+ automatic token refresh on authentication failures, and response processing.
23
+ Works in coordination with configurator for authentication and response
24
+ processor for parsing.
25
+ """
26
+
27
+ def __init__(self) -> None:
28
+ self._configurator = ClientDHCoreConfigurator()
29
+ self._response_processor = ResponseProcessor()
30
+
31
+ def prepare_request(self, method: str, api: str, **kwargs) -> dict:
32
+ """
33
+ Execute API call with full URL construction and authentication.
34
+
35
+ Parameters
36
+ ----------
37
+ method : str
38
+ HTTP method type (GET, POST, PUT, DELETE, etc.).
39
+ api : str
40
+ API endpoint path to call.
41
+ **kwargs : dict
42
+ Additional HTTP request arguments.
43
+
44
+ Returns
45
+ -------
46
+ dict
47
+ Response from the API call.
48
+ """
49
+ full_kwargs = self._set_auth(**kwargs)
50
+ url = self._build_url(api)
51
+ return self._execute_request(method, url, **full_kwargs)
52
+
53
+ def _execute_request(
54
+ self,
55
+ method: str,
56
+ url: str,
57
+ refresh: bool = True,
58
+ **kwargs,
59
+ ) -> dict:
60
+ """
61
+ Execute HTTP request with automatic handling.
62
+
63
+ Sends HTTP request with authentication, handles token refresh on 401 errors,
64
+ validates API version compatibility, and parses response. Uses 60-second
65
+ timeout by default.
66
+
67
+ Parameters
68
+ ----------
69
+ method : str
70
+ HTTP method (GET, POST, PUT, DELETE, etc.).
71
+ url : str
72
+ Complete URL to request.
73
+ refresh : bool, default True
74
+ Whether to attempt token refresh on authentication errors.
75
+ Set to False during refresh to prevent infinite recursion.
76
+ **kwargs : dict
77
+ Additional HTTP request arguments (headers, params, data, etc.).
78
+
79
+ Returns
80
+ -------
81
+ dict
82
+ Parsed response body as dictionary.
83
+ """
84
+ # Execute HTTP request
85
+ response = request(method, url, timeout=DEFAULT_TIMEOUT, **kwargs)
86
+
87
+ # Process response (version check, error parsing, dictify)
88
+ try:
89
+ return self._response_processor.process(response)
90
+ except BackendError as e:
91
+ # Handle authentication errors with token refresh
92
+ if response.status_code == 401 and refresh and self._configurator.refreshable_auth_types():
93
+ self._configurator.refresh_credentials(change_origin=True)
94
+ kwargs = self._configurator.get_auth_parameters(kwargs)
95
+ return self._execute_request(method, url, refresh=False, **kwargs)
96
+ raise e
97
+
98
+ def _set_auth(self, **kwargs) -> dict:
99
+ """
100
+ Prepare kwargs with authentication parameters.
101
+
102
+ Parameters
103
+ ----------
104
+ **kwargs : dict
105
+ Request parameters to augment with authentication.
106
+
107
+ Returns
108
+ -------
109
+ dict
110
+ kwargs enhanced with authentication parameters.
111
+ """
112
+ self._configurator.check_config()
113
+ return self._configurator.get_auth_parameters(kwargs)
114
+
115
+ def _build_url(self, api: str) -> str:
116
+ """
117
+ Build complete URL for API call.
118
+
119
+ Combines configured endpoint with API path, automatically removing
120
+ leading slashes for proper URL construction.
121
+
122
+ Parameters
123
+ ----------
124
+ api : str
125
+ API endpoint path. Leading slashes are automatically handled.
126
+
127
+ Returns
128
+ -------
129
+ str
130
+ Complete URL for the API call.
131
+ """
132
+ endpoint = self._configurator.get_endpoint()
133
+ return f"{endpoint}/{api.removeprefix('/')}"
@@ -0,0 +1,102 @@
1
+ # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ import typing
8
+ from warnings import warn
9
+
10
+ from requests.exceptions import JSONDecodeError
11
+
12
+ from digitalhub.stores.client.dhcore.error_parser import ErrorParser
13
+ from digitalhub.utils.exceptions import BackendError, ClientError
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from requests import Response
17
+
18
+
19
+ # API levels that are supported
20
+ MAX_API_LEVEL = 20
21
+ MIN_API_LEVEL = 14
22
+ LIB_VERSION = 14
23
+
24
+
25
+ class ResponseProcessor:
26
+ """
27
+ Processes and validates HTTP responses from DHCore backend.
28
+
29
+ Handles API version validation, error parsing, and response body parsing
30
+ to dictionary. Supports API versions {MIN_API_LEVEL} to {MAX_API_LEVEL}.
31
+ """
32
+
33
+ def __init__(self) -> None:
34
+ self._error_parser = ErrorParser()
35
+
36
+ def process(self, response: Response) -> dict:
37
+ """
38
+ Process HTTP response with validation and parsing.
39
+
40
+ Performs API version compatibility check, error parsing for failed
41
+ responses, and JSON deserialization.
42
+
43
+ Parameters
44
+ ----------
45
+ response : Response
46
+ HTTP response object from backend.
47
+
48
+ Returns
49
+ -------
50
+ dict
51
+ Parsed response body as dictionary.
52
+ """
53
+ self._check_api_version(response)
54
+ self._error_parser.parse(response)
55
+ return self._parse_json(response)
56
+
57
+ def _check_api_version(self, response: Response) -> None:
58
+ """
59
+ Validate DHCore API version compatibility.
60
+
61
+ Checks backend API version against supported range and warns if backend
62
+ version is newer than library. Supported: {MIN_API_LEVEL} to {MAX_API_LEVEL}.
63
+
64
+ Parameters
65
+ ----------
66
+ response : Response
67
+ HTTP response containing X-Api-Level header.
68
+ """
69
+ if "X-Api-Level" not in response.headers:
70
+ return
71
+
72
+ core_api_level = int(response.headers["X-Api-Level"])
73
+ if not (MIN_API_LEVEL <= core_api_level <= MAX_API_LEVEL):
74
+ raise ClientError("Backend API level not supported.")
75
+
76
+ if LIB_VERSION < core_api_level:
77
+ warn("Backend API level is higher than library version. You should consider updating the library.")
78
+
79
+ @staticmethod
80
+ def _parse_json(response: Response) -> dict:
81
+ """
82
+ Parse HTTP response body to dictionary.
83
+
84
+ Converts JSON response to Python dictionary, treating empty responses
85
+ as valid and returning empty dict.
86
+
87
+ Parameters
88
+ ----------
89
+ response : Response
90
+ HTTP response object to parse.
91
+
92
+ Returns
93
+ -------
94
+ dict
95
+ Parsed response body as dictionary, or empty dict if body is empty.
96
+ """
97
+ try:
98
+ return response.json()
99
+ except JSONDecodeError:
100
+ if response.text == "":
101
+ return {}
102
+ raise BackendError("Backend response could not be parsed.")
@@ -4,65 +4,12 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import os
8
7
  import typing
9
8
 
10
9
  from digitalhub.stores.client.api import get_client
11
- from digitalhub.stores.credentials.enums import CredsEnvVar
12
10
 
13
11
  if typing.TYPE_CHECKING:
14
- from digitalhub.stores.client.dhcore.client import ClientDHCore
15
-
16
-
17
- def set_dhcore_env(
18
- endpoint: str | None = None,
19
- user: str | None = None,
20
- password: str | None = None,
21
- access_token: str | None = None,
22
- refresh_token: str | None = None,
23
- client_id: str | None = None,
24
- ) -> None:
25
- """
26
- Set DHCore environment variables and reload client configuration.
27
-
28
- Updates environment variables for DHCore configuration and automatically
29
- reloads the client configurator to apply new settings. Overwrites existing
30
- environment variables if already set.
31
-
32
- Parameters
33
- ----------
34
- endpoint : str, optional
35
- DHCore backend endpoint URL.
36
- user : str, optional
37
- Username for basic authentication.
38
- password : str, optional
39
- Password for basic authentication.
40
- access_token : str, optional
41
- OAuth2 access token.
42
- refresh_token : str, optional
43
- OAuth2 refresh token.
44
- client_id : str, optional
45
- OAuth2 client identifier.
46
-
47
- Returns
48
- -------
49
- None
50
- """
51
- if endpoint is not None:
52
- os.environ[CredsEnvVar.DHCORE_ENDPOINT.value] = endpoint
53
- if user is not None:
54
- os.environ[CredsEnvVar.DHCORE_USER.value] = user
55
- if password is not None:
56
- os.environ[CredsEnvVar.DHCORE_PASSWORD.value] = password
57
- if access_token is not None:
58
- os.environ[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] = access_token
59
- if refresh_token is not None:
60
- os.environ[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] = refresh_token
61
- if client_id is not None:
62
- os.environ[CredsEnvVar.DHCORE_CLIENT_ID.value] = client_id
63
-
64
- client: ClientDHCore = get_client(local=False)
65
- client._configurator.load_env_vars()
12
+ pass
66
13
 
67
14
 
68
15
  def refresh_token() -> None:
@@ -72,15 +19,10 @@ def refresh_token() -> None:
72
19
  Uses the refresh token stored in client configuration to obtain a new
73
20
  access token. Requires OAuth2 authentication configuration.
74
21
 
75
- Returns
76
- -------
77
- None
78
22
 
79
23
  Raises
80
24
  ------
81
25
  ClientError
82
26
  If client not properly configured or token refresh fails.
83
27
  """
84
- client: ClientDHCore = get_client(local=False)
85
- client._configurator.check_config()
86
- client._configurator.refresh_credentials()
28
+ get_client(local=False).refresh_token()
@@ -570,9 +570,9 @@ class ClientLocal(Client):
570
570
  ----------
571
571
  error_code : int
572
572
  Error code identifying the type of error.
573
- entity_type : str, optional
573
+ entity_type : str
574
574
  Entity type that caused the error.
575
- entity_id : str, optional
575
+ entity_id : str
576
576
  Entity ID that caused the error.
577
577
 
578
578
  Returns
@@ -15,10 +15,6 @@ def set_current_profile(environment: str) -> None:
15
15
  ----------
16
16
  environment : str
17
17
  Name of the credentials profile to set.
18
-
19
- Returns
20
- -------
21
- None
22
18
  """
23
19
  creds_handler.set_current_profile(environment)
24
20
 
@@ -92,9 +92,6 @@ def write_config(creds: dict, environment: str) -> None:
92
92
  environment : str
93
93
  Name of the credentials profile/environment.
94
94
 
95
- Returns
96
- -------
97
- None
98
95
 
99
96
  Raises
100
97
  ------
@@ -129,9 +126,6 @@ def set_current_profile(environment: str) -> None:
129
126
  environment : str
130
127
  Name of the credentials profile to set as current.
131
128
 
132
- Returns
133
- -------
134
- None
135
129
 
136
130
  Raises
137
131
  ------
@@ -79,7 +79,7 @@ class StoreBuilder:
79
79
  The unique identifier for the store type (e.g., 's3', 'sql').
80
80
  store : Store
81
81
  The store class to register for this type.
82
- configurator : Configurator, optional
82
+ configurator : Configurator
83
83
  The configurator class for store configuration.
84
84
  If None, the store will be instantiated without configuration.
85
85
 
@@ -654,7 +654,7 @@ class S3Store(Store):
654
654
  ----------
655
655
  s3_path : str
656
656
  Path to the S3 bucket (e.g., 's3://bucket/path').
657
- retry : bool, optional
657
+ retry : bool
658
658
  Whether to retry the operation if a ConfigError is raised. Default is True.
659
659
 
660
660
  Returns
@@ -159,9 +159,9 @@ class SqlStore(Store):
159
159
  path : SourcesOrListOfSources
160
160
  The SQL URI path to read from in the format
161
161
  'sql://database/schema/table'. Only single paths are supported.
162
- file_format : str, optional
162
+ file_format : str
163
163
  File format specification (not used for SQL operations).
164
- engine : str, optional
164
+ engine : str
165
165
  DataFrame engine to use (e.g., 'pandas', 'polars').
166
166
  If None, uses the default engine.
167
167
  **kwargs : dict
@@ -209,7 +209,7 @@ class SqlStore(Store):
209
209
  path : str
210
210
  The SQL URI path specifying the database connection
211
211
  in the format 'sql://database/schema/table'.
212
- engine : str, optional
212
+ engine : str
213
213
  DataFrame engine to use for result processing
214
214
  (e.g., 'pandas', 'polars'). If None, uses the default.
215
215
 
@@ -238,7 +238,7 @@ class SqlStore(Store):
238
238
  dst : str
239
239
  The destination SQL URI in the format
240
240
  'sql://database/schema/table' or 'sql://database/table'.
241
- extension : str, optional
241
+ extension : str
242
242
  File extension parameter (not used for SQL operations).
243
243
  **kwargs : dict
244
244
  Additional keyword arguments passed to the DataFrame's
@@ -374,7 +374,7 @@ class SqlStore(Store):
374
374
 
375
375
  Parameters
376
376
  ----------
377
- schema : str, optional
377
+ schema : str
378
378
  The database schema to set in the search path.
379
379
  If provided, sets the PostgreSQL search_path option.
380
380
 
@@ -412,7 +412,7 @@ class SqlStore(Store):
412
412
  retry : bool, default True
413
413
  Whether to attempt a retry with different configuration
414
414
  if the initial connection fails.
415
- schema : str, optional
415
+ schema : str
416
416
  The database schema to configure in the engine.
417
417
 
418
418
  Returns
@@ -95,10 +95,6 @@ def requests_chunk_download(source: str, filename: Path) -> None:
95
95
  URL to download the file from.
96
96
  filename : Path
97
97
  Path where to save the downloaded file.
98
-
99
- Returns
100
- -------
101
- None
102
98
  """
103
99
  with requests.get(source, stream=True) as r:
104
100
  r.raise_for_status()
@@ -117,10 +113,6 @@ def extract_archive(path: Path, filename: Path) -> None:
117
113
  Directory where to extract the archive.
118
114
  filename : Path
119
115
  Path to the zip archive file.
120
-
121
- Returns
122
- -------
123
- None
124
116
  """
125
117
  with ZipFile(filename, "r") as zip_file:
126
118
  zip_file.extractall(path)
@@ -256,10 +248,6 @@ def carriage_return_warn(string: str) -> None:
256
248
  ----------
257
249
  string : str
258
250
  The string to check.
259
-
260
- Returns
261
- -------
262
- None
263
251
  """
264
252
  if "\r\n" in string:
265
253
  warn("String contains a carriage return. It may not be parsed correctly from remote runtimes.")
@@ -47,10 +47,6 @@ def clone_repository(path: Path, url: str) -> None:
47
47
  Path where to save the repository.
48
48
  url : str
49
49
  URL of the repository.
50
-
51
- Returns
52
- -------
53
- None
54
50
  """
55
51
  clean_path(path)
56
52
  checkout_object = get_checkout_object(url)
@@ -85,10 +81,6 @@ def clean_path(path: Path) -> None:
85
81
  ----------
86
82
  path : Path
87
83
  Path to clean.
88
-
89
- Returns
90
- -------
91
- None
92
84
  """
93
85
 
94
86
  shutil.rmtree(path, ignore_errors=True)
@@ -23,10 +23,6 @@ def write_yaml(filepath: str | Path, obj: dict | list[dict]) -> None:
23
23
  The YAML file path to write.
24
24
  obj : dict or list of dict
25
25
  The dict or list of dicts to write.
26
-
27
- Returns
28
- -------
29
- None
30
26
  """
31
27
  if isinstance(obj, list):
32
28
  with open(filepath, "w", encoding="utf-8") as out_file:
@@ -46,10 +42,6 @@ def write_text(filepath: Path, text: str) -> None:
46
42
  The file path to write.
47
43
  text : str
48
44
  The text to write.
49
-
50
- Returns
51
- -------
52
- None
53
45
  """
54
46
  filepath.write_text(text, encoding="utf-8")
55
47
 
@@ -32,7 +32,7 @@ def get_sql_engine(schema: str | None = None) -> Engine:
32
32
 
33
33
  Parameters
34
34
  ----------
35
- schema : str, optional
35
+ schema : str
36
36
  The schema to connect to, by default None.
37
37
 
38
38
  Returns
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalhub
3
- Version: 0.14.0b5
3
+ Version: 0.14.0b7
4
4
  Summary: Python SDK for Digitalhub
5
5
  Project-URL: Homepage, https://github.com/scc-digitalhub/digitalhub-sdk
6
6
  Author-email: Fondazione Bruno Kessler <digitalhub@fbk.eu>, Matteo Martini <mmartini@fbk.eu>