mosaic-python-client 0.2.0__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.
@@ -0,0 +1,342 @@
1
+ """
2
+ This module contains functions for interacting with the gPAS SOAP interface.
3
+ """
4
+
5
+ from zeep import Client
6
+
7
+ from mosaic_client.gpas.models import Domain, DomainResponse
8
+ from mosaic_client.gpas.schemas import DomainResponseSchema, DomainSchema
9
+ from mosaic_client.helpers import WSDLClient, _cast_client, _read_key_value_list, _serialize_dict
10
+
11
+ KeyValueTuple = tuple[str, str]
12
+
13
+
14
+ def delete_entry(client: Client, domain_name: str, value: str) -> None:
15
+ """
16
+ Deletes a value and its associated pseudonym from the specified data domain.
17
+
18
+ :param client: Zeep client with gPAS service definitions
19
+ :param domain_name: name of the domain where the value is present
20
+ :param value: value to remove
21
+ """
22
+ client.service.deleteEntry(domainName=domain_name, value=value)
23
+
24
+
25
+ def delete_entries(client: Client, domain_name: str, values: list[str]) -> list[KeyValueTuple]:
26
+ """
27
+ Deletes a list of values and their associated pseudonyms from the specified data domain.
28
+
29
+ :param client: Zeep client with gPAS service definitions
30
+ :param domain_name: name of the domain where the value is present
31
+ :param values: list of values to remove
32
+ :return: list of key-value pairs where the key is the value that was requested to be deleted, and the value is
33
+ an indicator whether deletion was successful or not
34
+ """
35
+ response = client.service.deleteEntries(domainName=domain_name, values=values)
36
+ return _read_key_value_list(_serialize_dict(response))
37
+
38
+
39
+ def get_or_create_pseudonym_for(client: Client, domain_name: str, value: str) -> str:
40
+ """
41
+ Requests a new pseudonym for a value in the specified data domain.
42
+
43
+ :param client: Zeep client with gPAS service definitions
44
+ :param domain_name: name of the domain where the value is supposed to be inserted
45
+ :param value: value to insert
46
+ :return: pseudonym that is assigned to the value
47
+ """
48
+ return client.service.getOrCreatePseudonymFor(domainName=domain_name, value=value)
49
+
50
+
51
+ def get_or_create_pseudonym_for_list(client: Client, domain_name: str, values: list[str]) -> list[KeyValueTuple]:
52
+ """
53
+ Request new pseudonyms for a list of values in the specified data domain.
54
+
55
+ :param client: Zeep client with gPAS service definitions
56
+ :param domain_name: name of the domain where the value is supposed to be inserted
57
+ :param values: list of values to insert
58
+ :return: list of key-value pairs where the key is the original value that is supposed to be pseudonymized,
59
+ and the value is the assigned pseudonym
60
+ """
61
+ response = client.service.getOrCreatePseudonymForList(domainName=domain_name, values=values)
62
+ return _read_key_value_list(_serialize_dict(response))
63
+
64
+
65
+ def get_value_for(client: Client, domain_name: str, pseudonym: str) -> str:
66
+ """
67
+ Gets the value for a pseudonym in the specified data domain.
68
+
69
+ :param client: Zeep client with gPAS service definitions
70
+ :param domain_name: name of the domain where the pseudonym is present
71
+ :param pseudonym: pseudonym to resolve
72
+ :return: value assigned to the pseudonym
73
+ """
74
+ return client.service.getValueFor(domainName=domain_name, psn=pseudonym)
75
+
76
+
77
+ def get_value_for_list(client: Client, domain_name: str, pseudonyms: list[str]) -> list[KeyValueTuple]:
78
+ """
79
+ Gets the values for a list of pseudonyms in the specified data domain.
80
+
81
+ :param client: Zeep client with gPAS service definitions
82
+ :param domain_name: name of the domain where the pseudonyms are present
83
+ :param pseudonyms: pseudonyms to resolve
84
+ :return: list of key-value pairs, structured as { pseudonym => value }
85
+ """
86
+ response = client.service.getValueForList(domainName=domain_name, psnList=pseudonyms)
87
+ return _read_key_value_list(_serialize_dict(response))
88
+
89
+
90
+ def get_pseudonym_for(client: Client, domain_name: str, value: str) -> str:
91
+ """
92
+ Gets the pseudonym for a value in the specified data domain.
93
+
94
+ :param client: Zeep client with gPAS service definitions
95
+ :param domain_name: name of the domain where the value is present
96
+ :param value: value to resolve
97
+ :return: pseudonym assigned to the value
98
+ """
99
+ return client.service.getPseudonymFor(domainName=domain_name, value=value)
100
+
101
+
102
+ def get_pseudonym_for_list(client: Client, domain_name: str, values: list[str]) -> list[KeyValueTuple]:
103
+ """
104
+ Gets the pseudonyms for a list of values in the specified data domain.
105
+
106
+ :param client: Zeep client with gPAS service definitions
107
+ :param domain_name: name of the domain where the values are present
108
+ :param values: values to resolve
109
+ :return: list of key-value pairs, structured as { value => pseudonym }
110
+ """
111
+ response = client.service.getPseudonymForList(domainName=domain_name, values=values)
112
+ return _read_key_value_list(_serialize_dict(response))
113
+
114
+
115
+ def insert_value_pseudonym_pair(client: Client, domain_name: str, value: str, pseudonym: str) -> None:
116
+ """
117
+ Manually inserts a value and a pseudonym into the specified data domain.
118
+
119
+ :param client: Zeep client with gPAS service definitions
120
+ :param domain_name: name of the domain to insert the pair into
121
+ :param value: value to add
122
+ :param pseudonym: pseudonym to assign to the value
123
+ """
124
+ client.service.insertValuePseudonymPair(domainName=domain_name, value=value, pseudonym=pseudonym)
125
+
126
+
127
+ def insert_value_pseudonym_pairs(client: Client, domain_name: str, pairs: list[KeyValueTuple]) -> None:
128
+ """
129
+ Manually inserts a list of values and pseudonyms into the specified data domain.
130
+
131
+ :param client: Zeep client with gPAS service definitions
132
+ :param domain_name: name of the domain to insert the pairs into
133
+ :param pairs: list of key-value pairs, structured as { value => pseudonym }
134
+ """
135
+ # for some reason this is the only case where the "entry" key is mandatory. it is not returned by any other
136
+ # endpoints where there's supposedly an "entry" key, e.g. deleteEntries (???)
137
+ client.service.insertValuePseudonymPairs(
138
+ domainName=domain_name,
139
+ pairs={
140
+ "entry": [
141
+ {
142
+ "key": kv_tuple[0],
143
+ "value": kv_tuple[1],
144
+ }
145
+ for kv_tuple in pairs
146
+ ]
147
+ },
148
+ )
149
+
150
+
151
+ def add_domain(client: Client, domain: Domain) -> None:
152
+ """
153
+ Adds a new domain.
154
+
155
+ :param client: Zeep client with gPAS domain service definitions
156
+ :param domain: domain data class to create
157
+ """
158
+ domain_soap = DomainSchema().dump(domain)
159
+ client.service.addDomain(domainDTO=domain_soap)
160
+
161
+
162
+ def get_domain(client: Client, domain_name: str) -> DomainResponse:
163
+ """
164
+ Gets a domain that matches the specified name. Raises a Fault if there is no domain with such a name.
165
+
166
+ :param client: Zeep client with gPAS domain service definitions
167
+ :param domain_name: name of the domain to get
168
+ :return: domain response instance
169
+ """
170
+ domain_soap = client.service.getDomain(domainName=domain_name)
171
+ return DomainResponseSchema().load(_serialize_dict(domain_soap))
172
+
173
+
174
+ def list_domains(client: Client) -> list[DomainResponse]:
175
+ """
176
+ Lists all available domains in the gPAS instance.
177
+
178
+ :param client: Zeep client with gPAS domain service definitions
179
+ :return: list of all available domains as domain response instances
180
+ """
181
+ domains = _serialize_dict(client.service.listDomains())
182
+ return [DomainResponseSchema().load(domain) for domain in domains]
183
+
184
+
185
+ def delete_domain(client: Client, domain_name: str) -> None:
186
+ """
187
+ Deletes a domain that matches the specified name. Raises a Fault if there is no domain with such a name.
188
+
189
+ :param client: Zeep client with gPAS domain service definitions
190
+ :param domain_name: name of the domain to delete
191
+ """
192
+ client.service.deleteDomainWithPSNs(domainName=domain_name)
193
+
194
+
195
+ class GPASClient(WSDLClient):
196
+ """
197
+ This class is a wrapper around the gPAS service functions.
198
+ """
199
+
200
+ def __init__(self, client: Client | str, domain_client: Client | str):
201
+ """
202
+ Constructs a new SOAP client for gPAS service definitions.
203
+
204
+ :param client: URL to WSDL endpoint or zeep instance with WSDL information for the gPAS service
205
+ :param domain_client: URL to WSDL endpoint or zeep instance with WSDL information for the gPAS management
206
+ service
207
+ """
208
+ super().__init__(client)
209
+ self._domain_client = _cast_client(domain_client)
210
+
211
+ def delete_entry(self, domain_name: str, value: str) -> None:
212
+ """
213
+ Deletes a value and its associated pseudonym from the specified data domain.
214
+
215
+ :param domain_name: name of the domain where the value is present
216
+ :param value: value to remove
217
+ """
218
+ delete_entry(self._client, domain_name, value)
219
+
220
+ def delete_entries(self, domain_name: str, values: list[str]) -> list[KeyValueTuple]:
221
+ """
222
+ Deletes a list of values and their associated pseudonyms from the specified data domain.
223
+
224
+ :param domain_name: name of the domain where the value is present
225
+ :param values: list of values to remove
226
+ :return: list of key-value pairs where the key is the value that was requested to be deleted, and the value is
227
+ an indicator whether deletion was successful or not
228
+ """
229
+ return delete_entries(self._client, domain_name, values)
230
+
231
+ def get_or_create_pseudonym_for(self, domain_name: str, value: str) -> str:
232
+ """
233
+ Requests a new pseudonym for a value in the specified data domain.
234
+
235
+ :param domain_name: name of the domain where the value is supposed to be inserted
236
+ :param value: value to insert
237
+ :return: pseudonym that is assigned to the value
238
+ """
239
+ return get_or_create_pseudonym_for(self._client, domain_name, value)
240
+
241
+ def get_or_create_pseudonym_for_list(self, domain_name: str, values: list[str]) -> list[KeyValueTuple]:
242
+ """
243
+ Request new pseudonyms for a list of values in the specified data domain.
244
+
245
+ :param domain_name: name of the domain where the value is supposed to be inserted
246
+ :param values: list of values to insert
247
+ :return: list of key-value pairs where the key is the original value that is supposed to be pseudonymized,
248
+ and the value is the assigned pseudonym
249
+ """
250
+ return get_or_create_pseudonym_for_list(self._client, domain_name, values)
251
+
252
+ def get_value_for(self, domain_name: str, pseudonym: str) -> str:
253
+ """
254
+ Gets the value for a pseudonym in the specified data domain.
255
+
256
+ :param domain_name: name of the domain where the pseudonym is present
257
+ :param pseudonym: pseudonym to resolve
258
+ :return: value assigned to the pseudonym
259
+ """
260
+ return get_value_for(self._client, domain_name, pseudonym)
261
+
262
+ def get_value_for_list(self, domain_name: str, pseudonyms: list[str]) -> list[KeyValueTuple]:
263
+ """
264
+ Gets the values for a list of pseudonyms in the specified data domain.
265
+
266
+ :param domain_name: name of the domain where the pseudonyms are present
267
+ :param pseudonyms: pseudonyms to resolve
268
+ :return: list of key-value pairs, structured as { pseudonym => value }
269
+ """
270
+ return get_value_for_list(self._client, domain_name, pseudonyms)
271
+
272
+ def get_pseudonym_for(self, domain_name: str, value: str) -> str:
273
+ """
274
+ Gets the pseudonym for a value in the specified data domain.
275
+
276
+ :param domain_name: name of the domain where the value is present
277
+ :param value: value to resolve
278
+ :return: pseudonym assigned to the value
279
+ """
280
+ return get_pseudonym_for(self._client, domain_name, value)
281
+
282
+ def get_pseudonym_for_list(self, domain_name: str, values: list[str]) -> list[KeyValueTuple]:
283
+ """
284
+ Gets the pseudonyms for a list of values in the specified data domain.
285
+
286
+ :param domain_name: name of the domain where the values are present
287
+ :param values: values to resolve
288
+ :return: list of key-value pairs, structured as { value => pseudonym }
289
+ """
290
+ return get_pseudonym_for_list(self._client, domain_name, values)
291
+
292
+ def insert_value_pseudonym_pair(self, domain_name: str, value: str, pseudonym: str) -> None:
293
+ """
294
+ Manually inserts a value and a pseudonym into the specified data domain.
295
+
296
+ :param domain_name: name of the domain to insert the pair into
297
+ :param value: value to add
298
+ :param pseudonym: pseudonym to assign to the value
299
+ """
300
+ insert_value_pseudonym_pair(self._client, domain_name, value, pseudonym)
301
+
302
+ def insert_value_pseudonym_pairs(self, domain_name: str, pairs: list[KeyValueTuple]) -> None:
303
+ """
304
+ Manually inserts a list of values and pseudonyms into the specified data domain.
305
+
306
+ :param domain_name: name of the domain to insert the pairs into
307
+ :param pairs: list of key-value pairs, structured as { value => pseudonym }
308
+ """
309
+ insert_value_pseudonym_pairs(self._client, domain_name, pairs)
310
+
311
+ def add_domain(self, domain: Domain) -> None:
312
+ """
313
+ Adds a new domain.
314
+
315
+ :param domain: domain data class to create
316
+ """
317
+ add_domain(self._domain_client, domain)
318
+
319
+ def get_domain(self, domain_name: str) -> DomainResponse:
320
+ """
321
+ Gets a domain that matches the specified name. Raises a Fault if there is no domain with such a name.
322
+
323
+ :param domain_name: name of the domain to get
324
+ :return: domain response instance
325
+ """
326
+ return get_domain(self._domain_client, domain_name)
327
+
328
+ def list_domains(self) -> list[DomainResponse]:
329
+ """
330
+ Lists all available domains in the gPAS instance.
331
+
332
+ :return: list of all available domains as domain response instances
333
+ """
334
+ return list_domains(self._domain_client)
335
+
336
+ def delete_domain(self, domain_name: str) -> None:
337
+ """
338
+ Deletes a domain that matches the specified name. Raises a Fault if there is no domain with such a name.
339
+
340
+ :param domain_name: name of the domain to delete
341
+ """
342
+ delete_domain(self._domain_client, domain_name)
@@ -0,0 +1,70 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime
3
+ from typing import Literal
4
+
5
+ Alphabet = (
6
+ Literal[
7
+ "org.emau.icmvc.ganimed.ttp.psn.alphabets.Hex",
8
+ "org.emau.icmvc.ganimed.ttp.psn.alphabets.Numbers",
9
+ "org.emau.icmvc.ganimed.ttp.psn.alphabets.NumbersWithoutZero",
10
+ "org.emau.icmvc.ganimed.ttp.psn.alphabets.NumbersX",
11
+ "org.emau.icmvc.ganimed.ttp.psn.alphabets.Symbol31",
12
+ "org.emau.icmvc.ganimed.ttp.psn.alphabets.Symbol32",
13
+ ]
14
+ | str
15
+ )
16
+ ForceCache = Literal["DEFAULT", "OFF", "ON"]
17
+ ValidateViaParents = Literal["CASCADE_DELETE", "ENSURE_EXISTS", "OFF", "VALIDATE"]
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class DomainConfig:
22
+ """
23
+ Configuration settings for gPAS domains.
24
+ """
25
+
26
+ psn_prefix: str | None = None
27
+ psn_suffix: str | None = None
28
+ include_prefix_in_check_digit_calculation: bool = False
29
+ include_suffix_in_check_digit_calculation: bool = False
30
+ max_detected_errors: int = 2
31
+ multi_psn_domain: bool = False
32
+ psn_length: int = 8
33
+ psns_deletable: bool = False
34
+ send_notifications_web: bool = False
35
+ use_last_char_as_delimiter_after_x_chars: int = 0
36
+ force_cache: ForceCache = "DEFAULT"
37
+ validate_values_via_parents: ValidateViaParents = "OFF"
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class Domain:
42
+ """
43
+ Describes a gPAS domain.
44
+ """
45
+
46
+ label: str
47
+ name: str
48
+ config: DomainConfig = field(default_factory=DomainConfig)
49
+ alphabet: Alphabet = "org.emau.icmvc.ganimed.ttp.psn.alphabets.Numbers"
50
+ check_digit_class: str = "org.emau.icmvc.ganimed.ttp.psn.generator.Verhoeff"
51
+ comment: str | None = None
52
+ parent_domain_names: list[str] = field(default_factory=list)
53
+ expiration_properties: None = None
54
+
55
+
56
+ @dataclass(frozen=True, kw_only=True)
57
+ class DomainResponse(Domain):
58
+ """
59
+ Further information about a domain that are available after a domain was created.
60
+ """
61
+
62
+ child_domain_names: list[str]
63
+ number_of_pseudonyms: int
64
+ number_of_anonyms: int
65
+ cache_used: bool
66
+ percent_psns_used: float
67
+ create_date: datetime
68
+ update_date: datetime
69
+ create_date_string: str
70
+ update_date_string: str
@@ -0,0 +1,53 @@
1
+ from marshmallow import Schema, fields, post_load
2
+
3
+ from mosaic_client.gpas.models import Domain, DomainConfig, DomainResponse
4
+
5
+
6
+ class DomainConfigSchema(Schema):
7
+ psn_prefix = fields.Str(data_key="psnPrefix", allow_none=True)
8
+ psn_suffix = fields.Str(data_key="psnSuffix", allow_none=True)
9
+ include_prefix_in_check_digit_calculation = fields.Bool(data_key="includePrefixInCheckDigitCalculation")
10
+ include_suffix_in_check_digit_calculation = fields.Bool(data_key="includeSuffixInCheckDigitCalculation")
11
+ max_detected_errors = fields.Int(data_key="maxDetectedErrors")
12
+ multi_psn_domain = fields.Bool(data_key="multiPsnDomain")
13
+ psn_length = fields.Int(data_key="psnLength")
14
+ psns_deletable = fields.Bool(data_key="psnsDeletable")
15
+ send_notifications_web = fields.Bool(data_key="sendNotificationsWeb")
16
+ use_last_char_as_delimiter_after_x_chars = fields.Int(data_key="useLastCharAsDelimiterAfterXChars")
17
+ validate_values_via_parents = fields.Str(data_key="validateValuesViaParents")
18
+ force_cache = fields.Str(data_key="forceCache")
19
+
20
+ @post_load
21
+ def make_domain_config(self, data, **kwargs) -> DomainConfig:
22
+ return DomainConfig(**data)
23
+
24
+
25
+ class DomainSchema(Schema):
26
+ label = fields.Str(required=True)
27
+ name = fields.Str(required=True)
28
+ config = fields.Nested(DomainConfigSchema())
29
+ alphabet = fields.Str()
30
+ check_digit_class = fields.Str(data_key="checkDigitClass")
31
+ comment = fields.Str(allow_none=True)
32
+ parent_domain_names = fields.List(fields.Str(), data_key="parentDomainNames")
33
+ expiration_properties = fields.Constant(None, data_key="expirationProperties")
34
+
35
+ @post_load
36
+ def make_domain(self, data, **kwargs) -> Domain:
37
+ return Domain(**data)
38
+
39
+
40
+ class DomainResponseSchema(DomainSchema):
41
+ child_domain_names = fields.List(fields.Str(), data_key="childDomainNames")
42
+ number_of_pseudonyms = fields.Int(data_key="numberOfPseudonyms")
43
+ number_of_anonyms = fields.Int(data_key="numberOfAnonyms")
44
+ cache_used = fields.Bool(data_key="cacheUsed")
45
+ percent_psns_used = fields.Float(data_key="percentPsnsUsed")
46
+ create_date = fields.DateTime(data_key="createDate")
47
+ update_date = fields.DateTime(data_key="updateDate")
48
+ create_date_string = fields.Str(data_key="createDateString")
49
+ update_date_string = fields.Str(data_key="updateDateString")
50
+
51
+ @post_load
52
+ def make_domain(self, data, **kwargs) -> DomainResponse:
53
+ return DomainResponse(**data)
@@ -0,0 +1,115 @@
1
+ """
2
+ This module contains helper functions that are used all throughout the mosaic_client library.
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ import zeep.helpers
8
+ from zeep import Client
9
+
10
+
11
+ def _serialize_dict(obj: Any) -> Any:
12
+ """
13
+ Converts a zeep response object into a dict or list, depending on the structure of the
14
+ response data.
15
+
16
+ :param obj: object to convert
17
+ :return: object as dict or list
18
+ """
19
+ return zeep.helpers.serialize_object(obj, dict)
20
+
21
+
22
+ def _malformed_key_value_response(reason: str) -> ValueError:
23
+ """
24
+ Returns a ValueError informing about an incorrectly structured key-value response.
25
+
26
+ :param reason: reason for error
27
+ :return: ValueError with specified reason
28
+ """
29
+ return ValueError(f"key-value response is malformed: {reason}")
30
+
31
+
32
+ def _read_key_value_list(entry_list: list) -> list[tuple[Any, Any]]:
33
+ """
34
+ Reads a list of objects structured as [{ "key", "value" }] into a list of tuples
35
+ where the first value of the tuple is the key and the second is the value. This function
36
+ will throw a ValueError if the list passed into this function is malformed.
37
+
38
+ :param entry_list: list of objects to parse
39
+ :return: list of key-value tuples
40
+ """
41
+ key_value_lst: list[tuple[Any, Any]] = []
42
+
43
+ for i in range(len(entry_list)):
44
+ entry = entry_list[i]
45
+
46
+ # check for "key" key
47
+ if "key" not in entry:
48
+ raise _malformed_key_value_response(f"'entry[{i}]' is missing 'key' attribute")
49
+
50
+ # check for "value" key
51
+ if "value" not in entry:
52
+ raise _malformed_key_value_response(f"'entry[{i}]' is missing 'value' attribute")
53
+
54
+ key_value_lst.append((entry["key"], entry["value"]))
55
+
56
+ return key_value_lst
57
+
58
+
59
+ def _read_key_value_entry_response(response: Any) -> list[tuple[Any, Any]]:
60
+ """
61
+ Reads an object structured as { "entry": [{ "key", "value" }] } into a list of tuples
62
+ where the first value of the tuple is the key and the second is the value. This function
63
+ will throw a ValueError if the object passed into this function is malformed.
64
+
65
+ :param response: object to parse
66
+ :return: list of key-value tuples
67
+ """
68
+ # check that it's actually a dict
69
+ if type(response) != dict:
70
+ raise _malformed_key_value_response("not an object")
71
+
72
+ # check for "entry" key
73
+ if "entry" not in response:
74
+ raise _malformed_key_value_response("'entry' attribute is missing")
75
+
76
+ # "entry" key should hold list of key-value entries
77
+ response_entry_lst = response["entry"]
78
+
79
+ # check that it's actually a list
80
+ if type(response_entry_lst) != list:
81
+ raise _malformed_key_value_response("'entry' value is not a list")
82
+
83
+ return _read_key_value_list(response_entry_lst)
84
+
85
+
86
+ def _cast_client(client: Client | str) -> Client:
87
+ """
88
+ Takes zeep client instances or wsdl urls as strings and either just returns the given client instance or creates
89
+ one if a string was given. Raises a TypeError if the given client is neither a zeep client nor a string.
90
+
91
+ :param client: client instance or wsdl url as a string
92
+ :return: zeep client instance
93
+ """
94
+ if isinstance(client, Client):
95
+ pass
96
+ elif isinstance(client, str):
97
+ client = Client(client)
98
+ else:
99
+ raise TypeError(f"unsupported constructor argument type: {type(client)}")
100
+
101
+ return client
102
+
103
+
104
+ class WSDLClient:
105
+ """
106
+ Generic SOAP client base.
107
+ """
108
+
109
+ def __init__(self, client: Client | str):
110
+ """
111
+ Constructs a new SOAP client.
112
+
113
+ :param client: URL to WSDL endpoint or zeep instance with WSDL information
114
+ """
115
+ self._client = _cast_client(client)