dapla-toolbelt-metadata 0.2.1__py3-none-any.whl → 0.9.11__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 dapla-toolbelt-metadata might be problematic. Click here for more details.
- dapla_metadata/__init__.py +11 -1
- dapla_metadata/_shared/__init__.py +1 -0
- dapla_metadata/_shared/config.py +109 -0
- dapla_metadata/_shared/enums.py +27 -0
- dapla_metadata/_shared/py.typed +0 -0
- dapla_metadata/dapla/__init__.py +4 -0
- dapla_metadata/dapla/user_info.py +138 -0
- dapla_metadata/datasets/__init__.py +1 -1
- dapla_metadata/datasets/_merge.py +333 -0
- dapla_metadata/datasets/code_list.py +5 -6
- dapla_metadata/datasets/compatibility/__init__.py +10 -0
- dapla_metadata/datasets/compatibility/_handlers.py +363 -0
- dapla_metadata/datasets/compatibility/_utils.py +259 -0
- dapla_metadata/datasets/compatibility/model_backwards_compatibility.py +135 -0
- dapla_metadata/datasets/core.py +136 -182
- dapla_metadata/datasets/dapla_dataset_path_info.py +145 -19
- dapla_metadata/datasets/dataset_parser.py +41 -28
- dapla_metadata/datasets/model_validation.py +29 -20
- dapla_metadata/datasets/statistic_subject_mapping.py +5 -1
- dapla_metadata/datasets/utility/constants.py +22 -15
- dapla_metadata/datasets/utility/enums.py +8 -20
- dapla_metadata/datasets/utility/urn.py +234 -0
- dapla_metadata/datasets/utility/utils.py +183 -111
- dapla_metadata/standards/__init__.py +4 -0
- dapla_metadata/standards/name_validator.py +250 -0
- dapla_metadata/standards/standard_validators.py +98 -0
- dapla_metadata/standards/utils/__init__.py +1 -0
- dapla_metadata/standards/utils/constants.py +49 -0
- dapla_metadata/variable_definitions/__init__.py +11 -0
- dapla_metadata/variable_definitions/_generated/.openapi-generator/FILES +20 -0
- dapla_metadata/variable_definitions/_generated/.openapi-generator/VERSION +1 -0
- dapla_metadata/variable_definitions/_generated/.openapi-generator-ignore +6 -0
- dapla_metadata/variable_definitions/_generated/README.md +148 -0
- dapla_metadata/variable_definitions/_generated/__init__.py +0 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/__init__.py +47 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api/__init__.py +8 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api/data_migration_api.py +766 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api/draft_variable_definitions_api.py +888 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api/patches_api.py +888 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api/validity_periods_api.py +583 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api/variable_definitions_api.py +613 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api_client.py +779 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/api_response.py +27 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/configuration.py +474 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/CompleteResponse.md +51 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/Contact.md +30 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/DataMigrationApi.md +90 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/Draft.md +42 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/DraftVariableDefinitionsApi.md +259 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/LanguageStringType.md +31 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/Owner.md +31 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/Patch.md +43 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/PatchesApi.md +249 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/PublicApi.md +218 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/SupportedLanguages.md +15 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/UpdateDraft.md +44 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/ValidityPeriod.md +42 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/ValidityPeriodsApi.md +236 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/VariableDefinitionsApi.md +304 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/docs/VariableStatus.md +17 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/exceptions.py +193 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/__init__.py +31 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/complete_response.py +260 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/contact.py +94 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/draft.py +228 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/get_vardok_vardef_mapping_by_id200_response.py +158 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/language_string_type.py +101 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/owner.py +87 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/patch.py +244 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/problem.py +118 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/update_draft.py +274 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/validity_period.py +225 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/vardok_id_response.py +81 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/vardok_vardef_id_pair_response.py +84 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/models/variable_status.py +33 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/py.typed +0 -0
- dapla_metadata/variable_definitions/_generated/vardef_client/rest.py +249 -0
- dapla_metadata/variable_definitions/_utils/__init__.py +1 -0
- dapla_metadata/variable_definitions/_utils/_client.py +32 -0
- dapla_metadata/variable_definitions/_utils/config.py +54 -0
- dapla_metadata/variable_definitions/_utils/constants.py +80 -0
- dapla_metadata/variable_definitions/_utils/files.py +309 -0
- dapla_metadata/variable_definitions/_utils/template_files.py +99 -0
- dapla_metadata/variable_definitions/_utils/variable_definition_files.py +143 -0
- dapla_metadata/variable_definitions/exceptions.py +255 -0
- dapla_metadata/variable_definitions/vardef.py +372 -0
- dapla_metadata/variable_definitions/vardok_id.py +48 -0
- dapla_metadata/variable_definitions/vardok_vardef_id_pair.py +47 -0
- dapla_metadata/variable_definitions/variable_definition.py +422 -0
- {dapla_toolbelt_metadata-0.2.1.dist-info → dapla_toolbelt_metadata-0.9.11.dist-info}/METADATA +34 -36
- dapla_toolbelt_metadata-0.9.11.dist-info/RECORD +97 -0
- {dapla_toolbelt_metadata-0.2.1.dist-info → dapla_toolbelt_metadata-0.9.11.dist-info}/WHEEL +1 -1
- dapla_metadata/datasets/config.py +0 -80
- dapla_metadata/datasets/model_backwards_compatibility.py +0 -520
- dapla_metadata/datasets/user_info.py +0 -88
- dapla_toolbelt_metadata-0.2.1.dist-info/RECORD +0 -22
- {dapla_toolbelt_metadata-0.2.1.dist-info → dapla_toolbelt_metadata-0.9.11.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Vardef client exceptions."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
|
|
8
|
+
import urllib3
|
|
9
|
+
from pytz import UnknownTimeZoneError
|
|
10
|
+
from ruamel.yaml.error import YAMLError
|
|
11
|
+
|
|
12
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.exceptions import (
|
|
13
|
+
OpenApiException,
|
|
14
|
+
)
|
|
15
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.exceptions import (
|
|
16
|
+
UnauthorizedException,
|
|
17
|
+
)
|
|
18
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.models.variable_status import (
|
|
19
|
+
VariableStatus,
|
|
20
|
+
)
|
|
21
|
+
from dapla_metadata.variable_definitions._utils._client import VardefClient
|
|
22
|
+
from dapla_metadata.variable_definitions._utils.constants import (
|
|
23
|
+
PUBLISHING_BLOCKED_ERROR_MESSAGE,
|
|
24
|
+
)
|
|
25
|
+
from dapla_metadata.variable_definitions._utils.constants import VARDEF_PROD_URL
|
|
26
|
+
|
|
27
|
+
# Use MappingProxyType so the dict is immutable
|
|
28
|
+
STATUS_EXPLANATIONS: MappingProxyType[HTTPStatus | None, str] = MappingProxyType(
|
|
29
|
+
{
|
|
30
|
+
HTTPStatus.BAD_REQUEST: "There was a problem with the supplied data. Please review the data you supplied based on the details below.",
|
|
31
|
+
HTTPStatus.UNAUTHORIZED: "There is a problem with your token, it may have expired. Try logging out, logging in and trying again.",
|
|
32
|
+
HTTPStatus.FORBIDDEN: "Forbidden. You don't have access to this, possibly because your team is not an owner of this variable definition.",
|
|
33
|
+
HTTPStatus.NOT_FOUND: "The variable definition was not found, check that the `short_name` or `id` is correct and that the variable exists.",
|
|
34
|
+
HTTPStatus.METHOD_NOT_ALLOWED: "That won't work here. The status of your variable definition likely doesn't allow for this to happen.",
|
|
35
|
+
HTTPStatus.CONFLICT: "There was a conflict with existing data. Please change your data and try again.",
|
|
36
|
+
None: "",
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class VardefClientError(Exception):
|
|
42
|
+
"""Custom exception to represent errors encountered in the Vardef client.
|
|
43
|
+
|
|
44
|
+
This exception extracts and formats error details from a JSON response body
|
|
45
|
+
provided by the Vardef API, enabling more descriptive error messages.
|
|
46
|
+
If the response body cannot be parsed as JSON or lacks expected keys,
|
|
47
|
+
default values are used to provide meaningful feedback.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, response_body: str) -> None:
|
|
51
|
+
"""Initialize the exception with a JSON-formatted response body.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
response_body (str): The JSON string containing error details
|
|
55
|
+
from the Vardef API response.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
status (str): The status code from the response, or "Unknown status"
|
|
59
|
+
if not available or the response is invalid.
|
|
60
|
+
detail (str || list): A detailed error message from the response, or
|
|
61
|
+
"No detail provided" if not provided. If "Constraint violation"
|
|
62
|
+
detail contains 'field' and 'message'.
|
|
63
|
+
response_body (str): The raw response body string, stored for
|
|
64
|
+
debugging purposes.
|
|
65
|
+
"""
|
|
66
|
+
self.status: int | None = None
|
|
67
|
+
self.detail: str = ""
|
|
68
|
+
try:
|
|
69
|
+
data = json.loads(response_body)
|
|
70
|
+
self.status = data.get("status")
|
|
71
|
+
if data.get("title") == "Constraint Violation":
|
|
72
|
+
violations = data.get("violations", [])
|
|
73
|
+
self.detail = "\n" + "\n".join(
|
|
74
|
+
f"{violation.get('field', 'Unknown field')}: {violation.get('message', 'No message provided')}"
|
|
75
|
+
for violation in violations
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
self.detail = data.get("detail")
|
|
79
|
+
|
|
80
|
+
if self.detail:
|
|
81
|
+
self.detail = f"\nDetail: {self.detail}"
|
|
82
|
+
self.response_body = response_body
|
|
83
|
+
except (json.JSONDecodeError, TypeError):
|
|
84
|
+
self.detail = "Could not decode error response from API"
|
|
85
|
+
|
|
86
|
+
super().__init__(
|
|
87
|
+
f"{self._get_status_explanation(self.status)}{self.detail if self.detail else ''}",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _get_status_explanation(status: int | None) -> str:
|
|
92
|
+
return (
|
|
93
|
+
""
|
|
94
|
+
if not status
|
|
95
|
+
else (STATUS_EXPLANATIONS.get(HTTPStatus(status)) or f"Status {status}:")
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def vardef_exception_handler(method): # noqa: ANN201, ANN001
|
|
100
|
+
"""Decorator for handling exceptions in Vardef."""
|
|
101
|
+
|
|
102
|
+
@wraps(method)
|
|
103
|
+
def _impl(self, *method_args, **method_kwargs): # noqa: ANN001, ANN002, ANN003
|
|
104
|
+
try:
|
|
105
|
+
return method(self, *method_args, **method_kwargs)
|
|
106
|
+
except urllib3.exceptions.HTTPError as e:
|
|
107
|
+
# Catch all urllib3 exceptions by catching the base class.
|
|
108
|
+
# These exceptions typically arise from lower level network problems.
|
|
109
|
+
raise VardefClientError(
|
|
110
|
+
json.dumps(
|
|
111
|
+
{
|
|
112
|
+
"status": None,
|
|
113
|
+
"title": "Network problems",
|
|
114
|
+
"detail": f"""There was a network problem when sending the request to the server. Try again shortly.
|
|
115
|
+
Original exception:
|
|
116
|
+
{getattr(e, "message", repr(e))}""",
|
|
117
|
+
},
|
|
118
|
+
),
|
|
119
|
+
) from e
|
|
120
|
+
except UnauthorizedException as e:
|
|
121
|
+
raise VardefClientError(
|
|
122
|
+
json.dumps(
|
|
123
|
+
{
|
|
124
|
+
"status": e.status,
|
|
125
|
+
"title": "Unauthorized",
|
|
126
|
+
"detail": "Unauthorized",
|
|
127
|
+
},
|
|
128
|
+
),
|
|
129
|
+
) from e
|
|
130
|
+
except OpenApiException as e:
|
|
131
|
+
raise VardefClientError(e.body) from e
|
|
132
|
+
|
|
133
|
+
return _impl
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class VariableNotFoundError(Exception):
|
|
137
|
+
"""Custom exception for when a variable is not found.
|
|
138
|
+
|
|
139
|
+
Attributes:
|
|
140
|
+
message (str): Message describing the error.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def __init__(self, message: str) -> None:
|
|
144
|
+
"""Initialize the VariableNotFoundError.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
message (str): Description of the error.
|
|
148
|
+
"""
|
|
149
|
+
super().__init__(message)
|
|
150
|
+
self.message = message
|
|
151
|
+
|
|
152
|
+
def __str__(self) -> str:
|
|
153
|
+
"""Return the string representation of the exception."""
|
|
154
|
+
return f" {self.message}"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class VardefFileError(Exception):
|
|
158
|
+
"""Custom exception for catching errors related to variable definition file handling.
|
|
159
|
+
|
|
160
|
+
Attributes:
|
|
161
|
+
message (str): Message describing the error.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, message: str, *args) -> None: # noqa: ANN002
|
|
165
|
+
"""Accepting the message and any additional arguments."""
|
|
166
|
+
super().__init__(message, *args)
|
|
167
|
+
self.message = message
|
|
168
|
+
self.args = args
|
|
169
|
+
|
|
170
|
+
def __str__(self) -> str:
|
|
171
|
+
"""Returning a custom string representation of the exception."""
|
|
172
|
+
return f"VardefFileError: {self.message}"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def vardef_file_error_handler(method): # noqa: ANN201, ANN001
|
|
176
|
+
"""Decorator for handling exceptions when generating yaml files for variable definitions."""
|
|
177
|
+
|
|
178
|
+
@wraps(method)
|
|
179
|
+
def _impl(*method_args, **method_kwargs): # noqa: ANN002, ANN003
|
|
180
|
+
try:
|
|
181
|
+
return method(
|
|
182
|
+
*method_args,
|
|
183
|
+
**method_kwargs,
|
|
184
|
+
)
|
|
185
|
+
except FileExistsError as e:
|
|
186
|
+
msg = f"File already exists and can not be saved: {method_kwargs.get('file_path', 'unknown file path')}"
|
|
187
|
+
raise VardefFileError(msg) from e
|
|
188
|
+
except PermissionError as e:
|
|
189
|
+
msg = f"Permission denied for file path when accessing the file. Original error: {e!s}"
|
|
190
|
+
raise VardefFileError(msg) from e
|
|
191
|
+
except UnknownTimeZoneError as e:
|
|
192
|
+
msg = f"Timezone is unknown: {method_kwargs.get('time_zone', 'unknown')}"
|
|
193
|
+
raise VardefFileError(msg) from e
|
|
194
|
+
except YAMLError as e:
|
|
195
|
+
msg = f"Invalid yaml. Please fix the formatting in your yaml file.\nOriginal error:\n{e!s}"
|
|
196
|
+
raise VardefFileError(msg) from e
|
|
197
|
+
except EOFError as e:
|
|
198
|
+
msg = "Unexpected end of file"
|
|
199
|
+
raise VardefFileError(msg) from e
|
|
200
|
+
except NotADirectoryError as e:
|
|
201
|
+
msg = f"Path is not a directory: {method_kwargs.get('file_path', 'unknown file path')}. Original error: {e!s}"
|
|
202
|
+
raise VardefFileError(msg) from e
|
|
203
|
+
|
|
204
|
+
return _impl
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class PublishingBlockedError(RuntimeError):
|
|
208
|
+
"""Exception raised when publishing variable definitions is blocked in the production environment."""
|
|
209
|
+
|
|
210
|
+
default_message = PUBLISHING_BLOCKED_ERROR_MESSAGE
|
|
211
|
+
|
|
212
|
+
def __str__(self) -> str:
|
|
213
|
+
"""Return the default publishing blocked error message."""
|
|
214
|
+
return self.default_message
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def publishing_blocked_error_handler(method): # noqa: ANN201, ANN001
|
|
218
|
+
"""Decorator that blocks publishing variable definitions in production.
|
|
219
|
+
|
|
220
|
+
- If the environment is production:
|
|
221
|
+
- If `variable_status` is present in the arguments and set to `PUBLISHED_INTERNAL` or `PUBLISHED_EXTERNAL`,
|
|
222
|
+
publishing is blocked by raising a `PublishingBlockedError`.
|
|
223
|
+
- If `variable_status` is set to `DRAFT`, the method is allowed to proceed.
|
|
224
|
+
- If no arguments are provided, all publishing attempts are blocked.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
@wraps(method)
|
|
228
|
+
def _impl(*method_args, **method_kwargs): # noqa: ANN002, ANN003
|
|
229
|
+
try:
|
|
230
|
+
client = VardefClient()
|
|
231
|
+
host = client.get_config().host
|
|
232
|
+
except VardefClientError as e:
|
|
233
|
+
msg = f"Failed to get VardefClient config: {e}"
|
|
234
|
+
raise RuntimeError(msg) from e
|
|
235
|
+
|
|
236
|
+
if host == VARDEF_PROD_URL:
|
|
237
|
+
if len(method_args) > 1:
|
|
238
|
+
status = method_args[1].variable_status
|
|
239
|
+
if status == VariableStatus.DRAFT:
|
|
240
|
+
return method(
|
|
241
|
+
*method_args,
|
|
242
|
+
**method_kwargs,
|
|
243
|
+
)
|
|
244
|
+
if status in (
|
|
245
|
+
VariableStatus.PUBLISHED_INTERNAL,
|
|
246
|
+
VariableStatus.PUBLISHED_EXTERNAL,
|
|
247
|
+
):
|
|
248
|
+
raise PublishingBlockedError
|
|
249
|
+
raise PublishingBlockedError
|
|
250
|
+
return method(
|
|
251
|
+
*method_args,
|
|
252
|
+
**method_kwargs,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return _impl
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import date
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.api.data_migration_api import (
|
|
7
|
+
DataMigrationApi,
|
|
8
|
+
)
|
|
9
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.api.draft_variable_definitions_api import (
|
|
10
|
+
DraftVariableDefinitionsApi,
|
|
11
|
+
)
|
|
12
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.api.variable_definitions_api import (
|
|
13
|
+
VariableDefinitionsApi,
|
|
14
|
+
)
|
|
15
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.models.complete_response import (
|
|
16
|
+
CompleteResponse,
|
|
17
|
+
)
|
|
18
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.models.draft import (
|
|
19
|
+
Draft,
|
|
20
|
+
)
|
|
21
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.models.vardok_id_response import (
|
|
22
|
+
VardokIdResponse,
|
|
23
|
+
)
|
|
24
|
+
from dapla_metadata.variable_definitions._utils import config
|
|
25
|
+
from dapla_metadata.variable_definitions._utils._client import VardefClient
|
|
26
|
+
from dapla_metadata.variable_definitions._utils.template_files import (
|
|
27
|
+
_find_latest_template_file,
|
|
28
|
+
)
|
|
29
|
+
from dapla_metadata.variable_definitions._utils.template_files import (
|
|
30
|
+
create_template_yaml,
|
|
31
|
+
)
|
|
32
|
+
from dapla_metadata.variable_definitions._utils.variable_definition_files import (
|
|
33
|
+
_read_file_to_model,
|
|
34
|
+
)
|
|
35
|
+
from dapla_metadata.variable_definitions.exceptions import VariableNotFoundError
|
|
36
|
+
from dapla_metadata.variable_definitions.exceptions import vardef_exception_handler
|
|
37
|
+
from dapla_metadata.variable_definitions.exceptions import vardef_file_error_handler
|
|
38
|
+
from dapla_metadata.variable_definitions.vardok_id import VardokId
|
|
39
|
+
from dapla_metadata.variable_definitions.vardok_vardef_id_pair import VardokVardefIdPair
|
|
40
|
+
from dapla_metadata.variable_definitions.variable_definition import VariableDefinition
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Vardef:
|
|
46
|
+
"""Create, maintain and read Variable Definitions.
|
|
47
|
+
|
|
48
|
+
====================
|
|
49
|
+
Variable Definitions
|
|
50
|
+
====================
|
|
51
|
+
|
|
52
|
+
Variable Definitions are centralized definitions of concrete variables which are typically present in multiple datasets. Variable Definitions
|
|
53
|
+
support standardization of data and metadata and facilitate sharing and joining of data by clarifying when variables have an identical
|
|
54
|
+
definition.
|
|
55
|
+
|
|
56
|
+
The methods in this class allow for creation, maintenance and access of Variable Definitions.
|
|
57
|
+
|
|
58
|
+
Creation and maintenance of variables may only be performed by Statistics Norway employees representing a specific Dapla team, who are defined
|
|
59
|
+
as the owners of a given Variable Definition. The group a user represents is chosen when starting a service in Dapla Lab. This class will
|
|
60
|
+
seamlessly detect this and send it to the API. All maintenance is to be performed by the owners, with no intervention from administrators.
|
|
61
|
+
|
|
62
|
+
======
|
|
63
|
+
Status
|
|
64
|
+
======
|
|
65
|
+
All Variable Definitions have an associated status. The possible values for status are `DRAFT`, `PUBLISHED_INTERNAL` and `PUBLISHED_EXTERNAL`.
|
|
66
|
+
|
|
67
|
+
-----
|
|
68
|
+
Draft
|
|
69
|
+
-----
|
|
70
|
+
When a Variable Definition is created it is assigned the status `DRAFT`. Under this status the Variable Definition is:
|
|
71
|
+
* Only visible to Statistics Norway employees.
|
|
72
|
+
* Mutable (it may be changed directly without need for versioning).
|
|
73
|
+
* Not suitable to refer to from other systems.
|
|
74
|
+
This status may be changed to `PUBLISHED_INTERNAL` or `PUBLISHED_EXTERNAL` with a direct update.
|
|
75
|
+
|
|
76
|
+
------------------
|
|
77
|
+
Published Internal
|
|
78
|
+
------------------
|
|
79
|
+
Under this status the Variable Definition is:
|
|
80
|
+
* Only visible to Statistics Norway employees.
|
|
81
|
+
* Immutable (all changes are versioned).
|
|
82
|
+
* Suitable to refer to in internal systems for statistics production.
|
|
83
|
+
* Not suitable to refer to for external use (for example in Statistikkbanken).
|
|
84
|
+
|
|
85
|
+
This status may be changed to `PUBLISHED_EXTERNAL` by creating a Patch version.
|
|
86
|
+
|
|
87
|
+
------------------
|
|
88
|
+
Published External
|
|
89
|
+
------------------
|
|
90
|
+
Under this status the Variable Definition is:
|
|
91
|
+
* Visible to the general public.
|
|
92
|
+
* Immutable (all changes are versioned).
|
|
93
|
+
* Suitable to refer to from any system.
|
|
94
|
+
|
|
95
|
+
This status may not be changed as it would break immutability. If a Variable Definition is no longer relevant then its period of validity
|
|
96
|
+
should be ended by specifying a `valid_until` date in a Patch version.
|
|
97
|
+
|
|
98
|
+
============
|
|
99
|
+
Immutability
|
|
100
|
+
============
|
|
101
|
+
Variable Definitions are immutable. This means that any changes must be performed in a strict versioning system. Consumers can avoid
|
|
102
|
+
being exposed to breaking changes by specifying a `date_of_validity` when they request a Variable Definition.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
@vardef_exception_handler
|
|
107
|
+
def create_draft(cls, draft: Draft) -> VariableDefinition:
|
|
108
|
+
"""Create a Draft Variable Definition."""
|
|
109
|
+
new_variable = VariableDefinition.from_model(
|
|
110
|
+
DraftVariableDefinitionsApi(
|
|
111
|
+
VardefClient.get_client(),
|
|
112
|
+
).create_variable_definition(
|
|
113
|
+
active_group=config.get_active_group(),
|
|
114
|
+
draft=draft,
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
logger.info(
|
|
119
|
+
"✅ Successfully created variable definition '%s' with ID '%s'",
|
|
120
|
+
new_variable.short_name,
|
|
121
|
+
new_variable.id,
|
|
122
|
+
)
|
|
123
|
+
return new_variable
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
@vardef_file_error_handler
|
|
127
|
+
def create_draft_from_file(
|
|
128
|
+
cls,
|
|
129
|
+
file_path: PathLike[str] | None = None,
|
|
130
|
+
) -> VariableDefinition:
|
|
131
|
+
"""Create a Draft Variable Definition from a stored yaml file.
|
|
132
|
+
|
|
133
|
+
By default the latest template file in the default directory is chosen, this may be overridden by providing a value for the optional `file_path` parameter.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
file_path (PathLike[str], optional): Supply a file path to override the automatic one. Defaults to None.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
FileNotFoundError: When a file can't be found.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
VariableDefinition: The created draft variable definition.
|
|
143
|
+
"""
|
|
144
|
+
return cls.create_draft(
|
|
145
|
+
_read_file_to_model(
|
|
146
|
+
file_path or _find_latest_template_file(),
|
|
147
|
+
Draft,
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
@vardef_exception_handler
|
|
153
|
+
def migrate_from_vardok(cls, vardok_id: str) -> VariableDefinition:
|
|
154
|
+
"""Migrate a Variable Definition from Vardok to Vardef.
|
|
155
|
+
|
|
156
|
+
- Each Vardok Variable Definition may only be migrated once.
|
|
157
|
+
- The Dapla team of the person who performs the migration will be set as the owner.
|
|
158
|
+
- All metadata should be checked for correctness before publication.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
vardok_id (str): The ID of a Variable Definition in Vardok.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
VariableDefinition: The migrated Variable Definition in Vardef.
|
|
165
|
+
"""
|
|
166
|
+
migrated_variable = VariableDefinition.from_model(
|
|
167
|
+
DataMigrationApi(
|
|
168
|
+
VardefClient.get_client(),
|
|
169
|
+
).create_variable_definition_from_var_dok(
|
|
170
|
+
active_group=config.get_active_group(),
|
|
171
|
+
vardok_id=vardok_id,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
logger.info(
|
|
176
|
+
"✅ Successfully migrated variable definition '%s' with ID '%s'",
|
|
177
|
+
migrated_variable.short_name,
|
|
178
|
+
migrated_variable.id,
|
|
179
|
+
)
|
|
180
|
+
return migrated_variable
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
@vardef_exception_handler
|
|
184
|
+
def list_vardok_vardef_mapping(cls) -> list[VardokVardefIdPair]:
|
|
185
|
+
"""List the mapping between vardok and vardef.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List[VardokVardefIdPair]: The list with mappings between Vardok and Vardef
|
|
189
|
+
"""
|
|
190
|
+
return [
|
|
191
|
+
VardokVardefIdPair.from_model(definition)
|
|
192
|
+
for definition in DataMigrationApi(
|
|
193
|
+
VardefClient.get_client(),
|
|
194
|
+
).get_vardok_vardef_mapping()
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
@vardef_exception_handler
|
|
199
|
+
def get_variable_definition_by_vardok_id(
|
|
200
|
+
cls,
|
|
201
|
+
vardok_id: str,
|
|
202
|
+
) -> VariableDefinition:
|
|
203
|
+
"""Get a Variable Definition by its Vardok ID.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
vardok_id (str): The Vardok ID of the desired Variable Definition
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
VariableDefinition: The Variable Definition.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
TypeError: If the incorrect type is returned.
|
|
213
|
+
"""
|
|
214
|
+
raw_response = DataMigrationApi(
|
|
215
|
+
VardefClient.get_client()
|
|
216
|
+
).get_vardok_vardef_mapping_by_id(
|
|
217
|
+
vardok_id,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if isinstance(raw_response.actual_instance, CompleteResponse):
|
|
221
|
+
return VariableDefinition.from_model(raw_response.actual_instance)
|
|
222
|
+
msg = "Unexpected response type"
|
|
223
|
+
raise TypeError(msg)
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
@vardef_exception_handler
|
|
227
|
+
def get_vardok_id_by_short_name(
|
|
228
|
+
cls,
|
|
229
|
+
short_name: str,
|
|
230
|
+
) -> VardokId:
|
|
231
|
+
"""Retrieve a Vardok id by short name.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
short_name (str): The short name of the desired Variable Definition
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
TypeError: If the incorrect type is returned.
|
|
238
|
+
"""
|
|
239
|
+
variable_definition = cls.get_variable_definition_by_shortname(short_name)
|
|
240
|
+
|
|
241
|
+
raw_response = DataMigrationApi(
|
|
242
|
+
VardefClient.get_client()
|
|
243
|
+
).get_vardok_vardef_mapping_by_id(
|
|
244
|
+
variable_definition.id,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if isinstance(raw_response.actual_instance, VardokIdResponse):
|
|
248
|
+
return VardokId.from_model(raw_response.actual_instance)
|
|
249
|
+
msg = "Unexpected response type"
|
|
250
|
+
raise TypeError(msg)
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
@vardef_exception_handler
|
|
254
|
+
def list_variable_definitions(
|
|
255
|
+
cls,
|
|
256
|
+
date_of_validity: date | None = None,
|
|
257
|
+
) -> list[VariableDefinition]:
|
|
258
|
+
"""List variable definitions.
|
|
259
|
+
|
|
260
|
+
---------
|
|
261
|
+
Filtering
|
|
262
|
+
---------
|
|
263
|
+
If no filter arguments are provided then all Variable Definitions are returned. See the documentation for the
|
|
264
|
+
individual arguments to understand their effect. Filter arguments are combined with AND logic.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
date_of_validity (date | None, optional): List only variable definitions which are valid on this date. Defaults to None.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
list[VariableDefinition]: The list of Variable Definitions.
|
|
271
|
+
"""
|
|
272
|
+
return [
|
|
273
|
+
VariableDefinition.from_model(definition)
|
|
274
|
+
for definition in VariableDefinitionsApi(
|
|
275
|
+
VardefClient.get_client(),
|
|
276
|
+
).list_variable_definitions(
|
|
277
|
+
date_of_validity=date_of_validity,
|
|
278
|
+
)
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
@vardef_exception_handler
|
|
283
|
+
def get_variable_definition_by_id(
|
|
284
|
+
cls,
|
|
285
|
+
variable_definition_id: str,
|
|
286
|
+
date_of_validity: date | None = None,
|
|
287
|
+
) -> VariableDefinition:
|
|
288
|
+
"""Get a Variable Definition by ID.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
variable_definition_id (str): The ID of the desired Variable Definition
|
|
292
|
+
date_of_validity (date | None, optional): List only variable definitions which are valid on this date. Defaults to None.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
VariableDefinition: The Variable Definition.
|
|
296
|
+
|
|
297
|
+
Raises:
|
|
298
|
+
NotFoundException when the given ID is not found
|
|
299
|
+
"""
|
|
300
|
+
return VariableDefinition.from_model(
|
|
301
|
+
VariableDefinitionsApi(
|
|
302
|
+
VardefClient.get_client(),
|
|
303
|
+
).get_variable_definition_by_id(
|
|
304
|
+
variable_definition_id=variable_definition_id,
|
|
305
|
+
date_of_validity=date_of_validity,
|
|
306
|
+
),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
@classmethod
|
|
310
|
+
@vardef_exception_handler
|
|
311
|
+
def get_variable_definition_by_shortname(
|
|
312
|
+
cls,
|
|
313
|
+
short_name: str,
|
|
314
|
+
date_of_validity: date | None = None,
|
|
315
|
+
) -> VariableDefinition:
|
|
316
|
+
"""Retrieve a Variable Definition by short name.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
short_name (str): The short name of the Variable Definition.
|
|
320
|
+
date_of_validity (date | None, optional): Filter by validity date. Defaults to None.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
VariableDefinition: The retrieved Variable Definition.
|
|
324
|
+
|
|
325
|
+
Raises:
|
|
326
|
+
VariableNotFoundError: If no matching Variable Definition is found.
|
|
327
|
+
ValueError: If multiple variables with the same shortname is found.
|
|
328
|
+
"""
|
|
329
|
+
client = VardefClient.get_client()
|
|
330
|
+
api = VariableDefinitionsApi(client)
|
|
331
|
+
|
|
332
|
+
variable_definitions = api.list_variable_definitions(
|
|
333
|
+
short_name=short_name,
|
|
334
|
+
date_of_validity=date_of_validity,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if not variable_definitions:
|
|
338
|
+
msg = f"Variable with short name {short_name} not found"
|
|
339
|
+
raise VariableNotFoundError(msg)
|
|
340
|
+
if len(variable_definitions) > 1:
|
|
341
|
+
msg = f"Lookup by short name {short_name} found multiple variables which should not be possible."
|
|
342
|
+
raise VariableNotFoundError(msg)
|
|
343
|
+
|
|
344
|
+
return VariableDefinition.from_model(variable_definitions[0])
|
|
345
|
+
|
|
346
|
+
@classmethod
|
|
347
|
+
@vardef_file_error_handler
|
|
348
|
+
def write_template_to_file(cls, custom_file_path: str | None = None) -> Path:
|
|
349
|
+
"""Write template with default values to a yaml file."""
|
|
350
|
+
file_path = create_template_yaml(
|
|
351
|
+
custom_directory=Path(custom_file_path) if custom_file_path else None,
|
|
352
|
+
)
|
|
353
|
+
logger.info(
|
|
354
|
+
f"✅ Created editable variable definition template file at {file_path}", # noqa: G004
|
|
355
|
+
)
|
|
356
|
+
return file_path
|
|
357
|
+
|
|
358
|
+
@classmethod
|
|
359
|
+
@vardef_exception_handler
|
|
360
|
+
def does_short_name_exist(
|
|
361
|
+
cls,
|
|
362
|
+
short_name: str,
|
|
363
|
+
) -> bool:
|
|
364
|
+
"""Return True if the short name exists in Vardef, otherwise False."""
|
|
365
|
+
variable_definitions = Vardef.list_variable_definitions()
|
|
366
|
+
for variable in variable_definitions:
|
|
367
|
+
if short_name.strip() == variable.short_name:
|
|
368
|
+
logger.info(
|
|
369
|
+
f"Found duplicate short name {short_name}", # noqa: G004
|
|
370
|
+
)
|
|
371
|
+
return True
|
|
372
|
+
return False
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pydantic import ConfigDict
|
|
5
|
+
from pydantic import PrivateAttr
|
|
6
|
+
|
|
7
|
+
from dapla_metadata.variable_definitions._generated.vardef_client.models.vardok_id_response import (
|
|
8
|
+
VardokIdResponse,
|
|
9
|
+
)
|
|
10
|
+
from dapla_metadata.variable_definitions._utils.variable_definition_files import (
|
|
11
|
+
_convert_to_yaml_output,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VardokId(VardokIdResponse):
|
|
18
|
+
"""A Vardok id.
|
|
19
|
+
|
|
20
|
+
- Provides access to the Vardok id filed.
|
|
21
|
+
- Provides methods allowing maintenance for nicer output of the Vardok id.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
VardokIdResponse: The Pydantic model superclass, representing a Vardok id response.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_file_path: Path | None = PrivateAttr(None)
|
|
28
|
+
|
|
29
|
+
model_config = ConfigDict(use_enum_values=True, str_strip_whitespace=True)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def from_model(
|
|
33
|
+
model: VardokIdResponse,
|
|
34
|
+
) -> "VardokId":
|
|
35
|
+
"""Create a VariableDefinition instance from a CompleteResponse."""
|
|
36
|
+
self = VardokId.from_dict(model.model_dump())
|
|
37
|
+
if not self:
|
|
38
|
+
msg = f"Could not construct a VardokId instance from {model}"
|
|
39
|
+
raise ValueError(msg)
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
def __str__(self) -> str:
|
|
43
|
+
"""Format as indented YAML."""
|
|
44
|
+
return _convert_to_yaml_output(self)
|
|
45
|
+
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
"""Format as indented YAML."""
|
|
48
|
+
return _convert_to_yaml_output(self)
|