datamasque-python 1.0.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.
- datamasque/client/__init__.py +204 -0
- datamasque/client/base.py +304 -0
- datamasque/client/connections.py +64 -0
- datamasque/client/discovery.py +286 -0
- datamasque/client/dmclient.py +49 -0
- datamasque/client/exceptions.py +75 -0
- datamasque/client/files.py +92 -0
- datamasque/client/ifm.py +301 -0
- datamasque/client/license.py +41 -0
- datamasque/client/models/__init__.py +0 -0
- datamasque/client/models/connection.py +429 -0
- datamasque/client/models/data_selection.py +62 -0
- datamasque/client/models/discovery.py +229 -0
- datamasque/client/models/dm_instance.py +39 -0
- datamasque/client/models/files.py +89 -0
- datamasque/client/models/ifm.py +177 -0
- datamasque/client/models/license.py +60 -0
- datamasque/client/models/pagination.py +29 -0
- datamasque/client/models/ruleset.py +45 -0
- datamasque/client/models/ruleset_library.py +22 -0
- datamasque/client/models/runs.py +165 -0
- datamasque/client/models/status.py +68 -0
- datamasque/client/models/user.py +69 -0
- datamasque/client/py.typed +0 -0
- datamasque/client/ruleset_libraries.py +164 -0
- datamasque/client/rulesets.py +57 -0
- datamasque/client/runs.py +189 -0
- datamasque/client/settings.py +76 -0
- datamasque/client/users.py +96 -0
- datamasque_python-1.0.0.dist-info/METADATA +113 -0
- datamasque_python-1.0.0.dist-info/RECORD +33 -0
- datamasque_python-1.0.0.dist-info/WHEEL +4 -0
- datamasque_python-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from datamasque.client.base import BaseClient
|
|
5
|
+
from datamasque.client.exceptions import (
|
|
6
|
+
FailedToStartError,
|
|
7
|
+
InvalidLibraryError,
|
|
8
|
+
InvalidRulesetError,
|
|
9
|
+
RunNotCancellableError,
|
|
10
|
+
)
|
|
11
|
+
from datamasque.client.models.runs import MaskingRunRequest, RunId, RunInfo, UnfinishedRun
|
|
12
|
+
from datamasque.client.models.status import MaskingRunStatus
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RunClient(BaseClient):
|
|
18
|
+
"""Masking-run and run-report API methods. Mixed into `DataMasqueClient`."""
|
|
19
|
+
|
|
20
|
+
def get_run_log(self, run_id: RunId) -> str:
|
|
21
|
+
"""Returns the full log output of the specified run."""
|
|
22
|
+
|
|
23
|
+
response = self.make_request("GET", f"api/runs/{run_id}/log/")
|
|
24
|
+
return response.text
|
|
25
|
+
|
|
26
|
+
def get_sdd_report(self, run_id: RunId) -> str:
|
|
27
|
+
"""Returns the sensitive-data-discovery report generated by the specified run."""
|
|
28
|
+
|
|
29
|
+
response = self.make_request("GET", f"api/runs/{run_id}/sdd-report/")
|
|
30
|
+
return response.text
|
|
31
|
+
|
|
32
|
+
def get_run_report(self, run_id: RunId) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Retrieves the run report for the specified run.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
run_id: The ID of the run
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
str: The run report content
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
response = self.make_request("GET", f"api/runs/{run_id}/run-report/")
|
|
44
|
+
return response.text
|
|
45
|
+
|
|
46
|
+
def get_db_discovery_result_report(self, run_id: RunId, include_selection_column: bool = True) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Returns the database-discovery result report for the specified run as CSV.
|
|
49
|
+
|
|
50
|
+
When `include_selection_column` is true (the default),
|
|
51
|
+
the CSV includes a `selected` column suitable for feeding back into ruleset generation.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
url = f"api/runs/{run_id}/db-discovery-results/report/"
|
|
55
|
+
params = None if include_selection_column else {"include_selection_column": "false"}
|
|
56
|
+
response = self.make_request("GET", url, params=params)
|
|
57
|
+
return response.text
|
|
58
|
+
|
|
59
|
+
def get_unfinished_runs(self) -> dict[str, UnfinishedRun]:
|
|
60
|
+
"""Queries the DM instance for unfinished runs, and returns them organised by connection name."""
|
|
61
|
+
|
|
62
|
+
unfinished_runs = {}
|
|
63
|
+
for status in (
|
|
64
|
+
MaskingRunStatus.queued,
|
|
65
|
+
MaskingRunStatus.running,
|
|
66
|
+
MaskingRunStatus.validating,
|
|
67
|
+
MaskingRunStatus.cancelling,
|
|
68
|
+
):
|
|
69
|
+
response = self.make_request(
|
|
70
|
+
"GET",
|
|
71
|
+
"api/runs/",
|
|
72
|
+
params={
|
|
73
|
+
"connection_ruleset_name": "",
|
|
74
|
+
"ruleset_name": "",
|
|
75
|
+
"run_status": status.value,
|
|
76
|
+
"limit": 1,
|
|
77
|
+
"offset": 0,
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
data = response.json()
|
|
81
|
+
|
|
82
|
+
for run in data.get("results", []):
|
|
83
|
+
unfinished_run = UnfinishedRun.model_validate(run)
|
|
84
|
+
|
|
85
|
+
unfinished_runs[unfinished_run.source_connection.name] = unfinished_run
|
|
86
|
+
if unfinished_run.destination_connection is not None:
|
|
87
|
+
unfinished_runs[unfinished_run.destination_connection.name] = unfinished_run
|
|
88
|
+
|
|
89
|
+
return unfinished_runs
|
|
90
|
+
|
|
91
|
+
def start_masking_run(self, run_info: MaskingRunRequest) -> RunId:
|
|
92
|
+
"""
|
|
93
|
+
Starts a masking run with the given configuration and returns its run ID.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
run_info: A `MaskingRunRequest` describing the run configuration.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
InvalidRulesetError: the run failed to start because the ruleset is invalid.
|
|
100
|
+
InvalidLibraryError: the run failed to start because a referenced library is invalid.
|
|
101
|
+
FailedToStartError: the run failed to start for any other reason.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
data = run_info.model_dump(exclude_none=True, mode="json")
|
|
105
|
+
response = self.make_request("POST", "/api/runs/", data=data, require_status_check=False)
|
|
106
|
+
run_data = response.json() if response.content else {}
|
|
107
|
+
|
|
108
|
+
if response.status_code == 201:
|
|
109
|
+
logger.info(
|
|
110
|
+
"Run %s started successfully using ruleset %s",
|
|
111
|
+
run_data["id"],
|
|
112
|
+
run_data["name"],
|
|
113
|
+
)
|
|
114
|
+
return RunId(run_data["id"])
|
|
115
|
+
|
|
116
|
+
if isinstance(run_data, dict) and "ruleset" in run_data:
|
|
117
|
+
logger.error("Run failed to start: %s", run_data)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
errors = run_data["ruleset"][0]
|
|
121
|
+
except (TypeError, IndexError, KeyError):
|
|
122
|
+
pass # fall through to generic FailedToStartError below
|
|
123
|
+
else:
|
|
124
|
+
if errors.lower().startswith("cannot start run"):
|
|
125
|
+
# Attempt to parse the library name out from a string like:
|
|
126
|
+
# `Library "abc" is invalid.`
|
|
127
|
+
# Trailing space is deliberate
|
|
128
|
+
# to match the end of the library name and the start of the error description that follows.
|
|
129
|
+
if matches := re.search(r'library "(.*)" ', errors, flags=re.IGNORECASE):
|
|
130
|
+
raise InvalidLibraryError(
|
|
131
|
+
f'Run failed to start due to invalid library named "{matches.group(1)}".',
|
|
132
|
+
response=response,
|
|
133
|
+
)
|
|
134
|
+
elif "library" in errors.lower():
|
|
135
|
+
raise InvalidLibraryError(
|
|
136
|
+
"Run failed to start due to invalid library.",
|
|
137
|
+
response=response,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
raise InvalidRulesetError(
|
|
141
|
+
f'Run failed to start due to invalid ruleset named "{data.get("name")}".',
|
|
142
|
+
response=response,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
raise FailedToStartError(
|
|
146
|
+
f'Run failed to start using ruleset named "{data.get("name")}" '
|
|
147
|
+
f"(server responded with status {response.status_code}: {response.text}).",
|
|
148
|
+
response=response,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def get_run_info(self, run_id: RunId) -> RunInfo:
|
|
152
|
+
"""Returns the full run record for the specified run ID."""
|
|
153
|
+
|
|
154
|
+
response = self.make_request("GET", f"/api/runs/{run_id}/")
|
|
155
|
+
return RunInfo.model_validate(response.json())
|
|
156
|
+
|
|
157
|
+
def cancel_run(self, run_id: RunId) -> RunInfo:
|
|
158
|
+
"""
|
|
159
|
+
Requests cancellation of the specified run and returns the updated run record.
|
|
160
|
+
|
|
161
|
+
On success the run transitions to the `cancelling` status;
|
|
162
|
+
callers can poll `get_run_info` to observe the final `cancelled` status.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
run_id: The ID of the run to cancel.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
The updated `RunInfo` for the run,
|
|
169
|
+
with `status` set to `cancelling`.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
RunNotCancellableError: the run is not in a state that can be cancelled
|
|
173
|
+
(typically because it is already finished, failed, or cancelling).
|
|
174
|
+
DataMasqueApiError: any other non-2xx response.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
response = self.make_request(
|
|
178
|
+
"POST",
|
|
179
|
+
f"/api/runs/{run_id}/cancel/",
|
|
180
|
+
require_status_check=False,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if response.status_code == 400:
|
|
184
|
+
# The admin server returns 400 with a `RunLifecycleError` payload
|
|
185
|
+
# when the run cannot be cancelled in its current state.
|
|
186
|
+
raise RunNotCancellableError(f"Run {run_id} cannot be cancelled in its current state.")
|
|
187
|
+
|
|
188
|
+
self._raise_for_status(response)
|
|
189
|
+
return RunInfo.model_validate(response.json())
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
from datamasque.client.base import BaseClient
|
|
6
|
+
from datamasque.client.exceptions import DataMasqueUserError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SettingsClient(BaseClient):
|
|
10
|
+
"""Server-wide settings, log retrieval, and admin-install bootstrap. Mixed into `DataMasqueClient`."""
|
|
11
|
+
|
|
12
|
+
def retrieve_application_logs(self, output_path: Path) -> None:
|
|
13
|
+
"""Downloads the DataMasque application logs archive to `output_path`."""
|
|
14
|
+
|
|
15
|
+
response = self.make_request("GET", path="/api/logs/download/", params={"log_service": "application"})
|
|
16
|
+
|
|
17
|
+
with open(output_path, "wb") as application_logs_output:
|
|
18
|
+
for chunk in response.iter_content(chunk_size=4096):
|
|
19
|
+
application_logs_output.write(chunk)
|
|
20
|
+
|
|
21
|
+
def set_locality(self, locality: str) -> None:
|
|
22
|
+
"""Sets the server-wide locality used for ruleset generation and Jinja2 interpolation of ruleset YAML."""
|
|
23
|
+
|
|
24
|
+
self.make_request("PATCH", path="api/settings/", data={"locality": locality})
|
|
25
|
+
|
|
26
|
+
def admin_install(
|
|
27
|
+
self,
|
|
28
|
+
email: str,
|
|
29
|
+
username: str = "admin",
|
|
30
|
+
password: Optional[str] = None,
|
|
31
|
+
allowed_hosts: Optional[list[str]] = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Performs the first-time admin-install bootstrap on a fresh DataMasque server.
|
|
35
|
+
|
|
36
|
+
Creates the initial admin account and configures the server's allowed-hosts list.
|
|
37
|
+
This endpoint is unauthenticated and can only be called once per server;
|
|
38
|
+
subsequent calls will fail.
|
|
39
|
+
|
|
40
|
+
If `password` is not given, the client's configured password is used.
|
|
41
|
+
If `allowed_hosts` is not given, it defaults to the following list:
|
|
42
|
+
|
|
43
|
+
- `localhost`
|
|
44
|
+
- `127.0.0.1`
|
|
45
|
+
- the client's configured hostname (from `base_url`).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
if password is None:
|
|
49
|
+
password = self.password
|
|
50
|
+
if password is None:
|
|
51
|
+
# Clients constructed with `token_source` instead of a password
|
|
52
|
+
# have no fallback to use here; require an explicit `password` argument.
|
|
53
|
+
raise DataMasqueUserError(
|
|
54
|
+
"`admin_install` requires a `password` argument when the client was constructed without one."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if allowed_hosts is None:
|
|
58
|
+
allowed_hosts = ["localhost", "127.0.0.1"]
|
|
59
|
+
dm_hostname = urlparse(self.base_url).hostname
|
|
60
|
+
if dm_hostname and dm_hostname not in allowed_hosts:
|
|
61
|
+
allowed_hosts.append(dm_hostname)
|
|
62
|
+
|
|
63
|
+
data = {
|
|
64
|
+
"email": email,
|
|
65
|
+
"username": username,
|
|
66
|
+
"password": password,
|
|
67
|
+
"re_password": password,
|
|
68
|
+
"allowed_hosts": allowed_hosts,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
self.make_request(
|
|
72
|
+
"POST",
|
|
73
|
+
"/api/users/admin-install/",
|
|
74
|
+
data=data,
|
|
75
|
+
requires_authorization=False,
|
|
76
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from datamasque.client.base import BaseClient
|
|
4
|
+
from datamasque.client.exceptions import DataMasqueException, DataMasqueUserError
|
|
5
|
+
from datamasque.client.models.user import User, UserId, UserRole
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserClient(BaseClient):
|
|
9
|
+
"""User CRUD API methods. Mixed into `DataMasqueClient`."""
|
|
10
|
+
|
|
11
|
+
def list_users(self) -> list[User]:
|
|
12
|
+
"""Returns all active users configured on the server."""
|
|
13
|
+
|
|
14
|
+
users = []
|
|
15
|
+
for user_data in self.make_request("GET", "/api/users/").json():
|
|
16
|
+
if user_data.get("is_active", False):
|
|
17
|
+
users.append(User.model_validate(user_data))
|
|
18
|
+
|
|
19
|
+
return users
|
|
20
|
+
|
|
21
|
+
def create_or_update_user(self, user: User, new_password: Optional[str] = None) -> User:
|
|
22
|
+
"""
|
|
23
|
+
Creates or updates the user.
|
|
24
|
+
|
|
25
|
+
An update will be performed if `user.id` is set, otherwise a create.
|
|
26
|
+
To also set the user's password,
|
|
27
|
+
put the old password in the user's `password` field (for an existing user)
|
|
28
|
+
and pass the new password in the `new_password` parameter.
|
|
29
|
+
Returns the same User object but with the id and password fields populated.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
if not user.roles:
|
|
33
|
+
raise DataMasqueUserError("User must have at least one role")
|
|
34
|
+
if UserRole.ruleset_library_manager in user.roles and UserRole.mask_builder not in user.roles:
|
|
35
|
+
raise DataMasqueUserError("`ruleset_library_manager` role requires `mask_builder` role")
|
|
36
|
+
|
|
37
|
+
if user.id is None:
|
|
38
|
+
temp_password = User.generate_password()
|
|
39
|
+
|
|
40
|
+
data = user.model_dump(exclude_none=True, by_alias=True, mode="json") | {
|
|
41
|
+
"password": temp_password,
|
|
42
|
+
"re_password": temp_password,
|
|
43
|
+
}
|
|
44
|
+
resp = self.make_request("POST", "/api/users/", data=data).json()
|
|
45
|
+
user.id = resp["id"]
|
|
46
|
+
user.password = temp_password
|
|
47
|
+
else:
|
|
48
|
+
self.make_request(
|
|
49
|
+
"PATCH",
|
|
50
|
+
f"/api/users/{user.id}/",
|
|
51
|
+
data=user.model_dump(exclude_none=True, by_alias=True, mode="json"),
|
|
52
|
+
).json()
|
|
53
|
+
|
|
54
|
+
if new_password:
|
|
55
|
+
self.make_request(
|
|
56
|
+
"PATCH",
|
|
57
|
+
f"/api/users/{user.id}/",
|
|
58
|
+
data={
|
|
59
|
+
"current_password": user.password,
|
|
60
|
+
"new_password": new_password,
|
|
61
|
+
"re_new_password": new_password,
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
user.password = new_password
|
|
65
|
+
|
|
66
|
+
return user
|
|
67
|
+
|
|
68
|
+
def reset_password_for_user(self, user: User) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Resets the user's password.
|
|
71
|
+
|
|
72
|
+
The temporary password is stored on the User object and also returned.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
if user.id is None:
|
|
76
|
+
raise DataMasqueUserError("User must be created first")
|
|
77
|
+
|
|
78
|
+
resp = self.make_request("POST", f"/api/users/{user.id}/reset-password/").json()
|
|
79
|
+
user.password = resp["password"]
|
|
80
|
+
return user.password
|
|
81
|
+
|
|
82
|
+
def delete_user_by_id_if_exists(self, user_id: UserId) -> None:
|
|
83
|
+
"""Deletes the user with the given ID. No-op if the user does not exist."""
|
|
84
|
+
|
|
85
|
+
self._delete_if_exists(f"/api/users/{user_id}/")
|
|
86
|
+
|
|
87
|
+
def delete_user_by_username_if_exists(self, username: str) -> None:
|
|
88
|
+
"""Deletes the user with the given username. No-op if the user does not exist."""
|
|
89
|
+
|
|
90
|
+
all_users = self.list_users()
|
|
91
|
+
users_matching_username = [u for u in all_users if u.username == username]
|
|
92
|
+
for user in users_matching_username:
|
|
93
|
+
if user.id is None:
|
|
94
|
+
raise DataMasqueException(f'Server returned a user named "{user.username}" without an `id`.')
|
|
95
|
+
|
|
96
|
+
self.delete_user_by_id_if_exists(user.id)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datamasque-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python client for the DataMasque data-masking API.
|
|
5
|
+
Project-URL: Homepage, https://datamasque.com/
|
|
6
|
+
Project-URL: Documentation, https://datamasque-python.readthedocs.io/
|
|
7
|
+
Project-URL: Repository, https://github.com/datamasque/datamasque-python
|
|
8
|
+
Project-URL: Issues, https://github.com/datamasque/datamasque-python/issues
|
|
9
|
+
Author: DataMasque Ltd
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api-client,data-masking,data-privacy,datamasque,synthetic-data,test-data
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Security
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Requires-Dist: pydantic<3,>=2.5
|
|
30
|
+
Requires-Dist: requests>=2.31.0
|
|
31
|
+
Description-Content-Type: text/x-rst
|
|
32
|
+
|
|
33
|
+
=================
|
|
34
|
+
datamasque-python
|
|
35
|
+
=================
|
|
36
|
+
|
|
37
|
+
Official Python client for the `DataMasque <https://datamasque.com/>`_ platform.
|
|
38
|
+
|
|
39
|
+
DataMasque is a data masking platform that replaces sensitive data with realistic but non-production values,
|
|
40
|
+
so teams can use production-shaped data in non-production environments without exposing PII.
|
|
41
|
+
This package is a thin Python wrapper around the DataMasque server's HTTP API,
|
|
42
|
+
covering connection management, ruleset and ruleset-library CRUD,
|
|
43
|
+
masking run lifecycle, discovery results, user administration, and license management.
|
|
44
|
+
|
|
45
|
+
Installation
|
|
46
|
+
============
|
|
47
|
+
|
|
48
|
+
.. code-block:: console
|
|
49
|
+
|
|
50
|
+
pip install datamasque-python
|
|
51
|
+
|
|
52
|
+
Python 3.9 or newer is required.
|
|
53
|
+
|
|
54
|
+
Quickstart
|
|
55
|
+
==========
|
|
56
|
+
|
|
57
|
+
.. code-block:: python
|
|
58
|
+
|
|
59
|
+
from datamasque.client import DataMasqueClient
|
|
60
|
+
from datamasque.client.models.dm_instance import DataMasqueInstanceConfig
|
|
61
|
+
|
|
62
|
+
config = DataMasqueInstanceConfig(
|
|
63
|
+
base_url="https://datamasque.example.com",
|
|
64
|
+
username="api_user",
|
|
65
|
+
password="api_password",
|
|
66
|
+
)
|
|
67
|
+
client = DataMasqueClient(config)
|
|
68
|
+
client.authenticate()
|
|
69
|
+
|
|
70
|
+
for connection in client.list_connections():
|
|
71
|
+
print(connection.name)
|
|
72
|
+
|
|
73
|
+
Authentication is performed on the first request if ``authenticate()`` is not called explicitly,
|
|
74
|
+
and is automatically retried once on a 401 response.
|
|
75
|
+
``client.healthcheck()`` is available as a lightweight readiness probe that does not consume credentials.
|
|
76
|
+
|
|
77
|
+
Error handling
|
|
78
|
+
==============
|
|
79
|
+
|
|
80
|
+
All methods raise subclasses of ``DataMasqueException`` on failure:
|
|
81
|
+
|
|
82
|
+
- ``DataMasqueApiError`` —
|
|
83
|
+
the server responded with a non-2xx status (excluding 502).
|
|
84
|
+
The triggering ``Response`` is available on the ``.response`` attribute.
|
|
85
|
+
- ``DataMasqueNotReadyError`` —
|
|
86
|
+
the server responded with 502,
|
|
87
|
+
typically because it is still starting up.
|
|
88
|
+
- ``DataMasqueTransportError`` —
|
|
89
|
+
the request failed before any response was received
|
|
90
|
+
(connection refused, timeout, DNS failure, SSL handshake failure, etc.).
|
|
91
|
+
- ``FailedToStartError`` / ``InvalidRulesetError`` / ``InvalidLibraryError`` —
|
|
92
|
+
raised by ``start_masking_run`` when the server rejects the run.
|
|
93
|
+
- ``DataMasqueUserError`` —
|
|
94
|
+
raised by user-management methods when the input is invalid.
|
|
95
|
+
|
|
96
|
+
Documentation
|
|
97
|
+
=============
|
|
98
|
+
|
|
99
|
+
- All classes and functions have docstrings and type hints.
|
|
100
|
+
- Compiled docs are hosted at `Read the Docs: datamasque-python <https://datamasque-python.readthedocs.io/>`_.
|
|
101
|
+
- Documentation for the DataMasque product, including a full API reference,
|
|
102
|
+
can be found on the `DataMasque portal <https://portal.datamasque.com/portal/documentation/>`_.
|
|
103
|
+
|
|
104
|
+
Contributing
|
|
105
|
+
============
|
|
106
|
+
|
|
107
|
+
See `CONTRIBUTING.rst <CONTRIBUTING.rst>`_ for development setup, testing, and the pull request flow.
|
|
108
|
+
|
|
109
|
+
License
|
|
110
|
+
=======
|
|
111
|
+
|
|
112
|
+
Apache License 2.0.
|
|
113
|
+
See `LICENSE <LICENSE>`_.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
datamasque/client/__init__.py,sha256=jg4BxNWHon9V90a8-dhlzKldhkmlRPku6cFfk1IQ_Vs,5406
|
|
2
|
+
datamasque/client/base.py,sha256=he-ObABs_rpXV_KSMmrtJ73D-9lb0giAK029lN5qB3Y,11817
|
|
3
|
+
datamasque/client/connections.py,sha256=EFinx8fJRme0mTxuWY3d29UnmUFbsQhMaUQT0Ma2PK4,2885
|
|
4
|
+
datamasque/client/discovery.py,sha256=uA8h6vRqsxSAzSAV9bebJwU47XINlaVy7V1nTYDiaCM,12634
|
|
5
|
+
datamasque/client/dmclient.py,sha256=OPYMzc57gUHPs6iL_J2DYp06MfOaabpTlISmnNCpqS4,1553
|
|
6
|
+
datamasque/client/exceptions.py,sha256=F9FYCxP-ERXkVD1L3yh_rWqcW3IsPL-c-Ic4qMYoGnw,2542
|
|
7
|
+
datamasque/client/files.py,sha256=5Gzel4aLby8T7ncOIV6wtgVbFEk0eaEy7wxohh9u0zE,3468
|
|
8
|
+
datamasque/client/ifm.py,sha256=K4n9q-8f2wsmxnKlE-jkn9zl40iC9RGAw2tRB0mAtyM,11737
|
|
9
|
+
datamasque/client/license.py,sha256=pluYaSU168OC6_laB9bM9H3Vuuxs8wa9gujCJvtoJCk,1392
|
|
10
|
+
datamasque/client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
datamasque/client/ruleset_libraries.py,sha256=tyN--cndzG0gwFnHY9fDtObTLzGsK0wrV5lxxjRP72g,6868
|
|
12
|
+
datamasque/client/rulesets.py,sha256=2Zh6QUihZ8p3dNT6QbaWuxvMk-Whp67JUj8UxqeRYUY,2407
|
|
13
|
+
datamasque/client/runs.py,sha256=ZPSkkuyqMiwy7dLbWZ1PAEKaesKLeNRX7xEJGfzieVg,7427
|
|
14
|
+
datamasque/client/settings.py,sha256=Ui8AyR2XdoW8MZ9FIrGn2jm8DLzUGWIL8i2DOTvu7hc,2898
|
|
15
|
+
datamasque/client/users.py,sha256=VCUo2CJyOw4-aO_3mp_w_0BcSoa6-h1HcReMcAMyumw,3701
|
|
16
|
+
datamasque/client/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
datamasque/client/models/connection.py,sha256=HmpojGKQpL9BpDCkLGPNInyBwc98QTpeNKVnqcuHNOw,14680
|
|
18
|
+
datamasque/client/models/data_selection.py,sha256=406yyUZ5NmLBSql2lYM1gsTWY5GWmnutmF-DjznAoLc,1904
|
|
19
|
+
datamasque/client/models/discovery.py,sha256=BawKusPuhyt0gRRnWKYf-ZKF7V54GxkhgYIrEQ6XkOs,7283
|
|
20
|
+
datamasque/client/models/dm_instance.py,sha256=yjjpHZJTFhJp3lAinTEffgKrxnadrJys1EuhO77wQcA,1561
|
|
21
|
+
datamasque/client/models/files.py,sha256=OaWVD-AX77vvaN_qxB6Uzn2HRasb7wNo-6QAvRzV3og,2381
|
|
22
|
+
datamasque/client/models/ifm.py,sha256=j0Ef2BZYTk6MNZO6HS6mW0qWs1_wWD9PlUP_Y6WyBQI,5563
|
|
23
|
+
datamasque/client/models/license.py,sha256=OqIn4Sx3ATBStgt5KNiCOBChTTFYLBsyGj7inAl8oB4,2113
|
|
24
|
+
datamasque/client/models/pagination.py,sha256=egg9aO2cf6KUDwDANPu5RxpJbdRKdwUdCQAtusnuf1c,651
|
|
25
|
+
datamasque/client/models/ruleset.py,sha256=tf5j9ih3B0uEF4yPTnyCsLblFWzH-t9KnvJWuyJaiCs,1256
|
|
26
|
+
datamasque/client/models/ruleset_library.py,sha256=gIqb6yn4-f9i2OydOLk1cd0zId9nGv5l_yUZwFHZ1aA,652
|
|
27
|
+
datamasque/client/models/runs.py,sha256=oVYo9jp9s5LjWts0LKsYpX3HUBmrVwuuzDqFtQmTjvo,5673
|
|
28
|
+
datamasque/client/models/status.py,sha256=rjH6YSwAHoOUaUCADwvMhuKd0ygT0c2w2Ek5SV8PWD8,1984
|
|
29
|
+
datamasque/client/models/user.py,sha256=UGAUzgJkf78m24_zFXXoA99zdut48BXkX_ivV8yq1Vc,2043
|
|
30
|
+
datamasque_python-1.0.0.dist-info/METADATA,sha256=XmCNVYLLUjAKQgad5KXfo-zC7Hp17g0w-urVdbxmgZ8,4187
|
|
31
|
+
datamasque_python-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
32
|
+
datamasque_python-1.0.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
33
|
+
datamasque_python-1.0.0.dist-info/RECORD,,
|