datamasque-python 1.1.0__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/HISTORY.rst +9 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/PKG-INFO +1 -1
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/discovery.py +14 -2
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/discovery.py +3 -3
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/status.py +1 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/runs.py +9 -2
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/pyproject.toml +1 -1
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/setup.cfg +1 -1
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_discovery.py +36 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/uv.lock +1 -1
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/.editorconfig +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/.github/workflows/ci.yml +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/.github/workflows/release-testpypi.yml +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/.github/workflows/release.yml +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/.gitignore +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/.readthedocs.yaml +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/CONTRIBUTING.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/LICENSE +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/MANIFEST.in +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/Makefile +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/NOTICE +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/README.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/__init__.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/base.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/connections.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/discovery_configs.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/dmclient.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/exceptions.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/files.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/ifm.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/license.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/__init__.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/connection.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/data_selection.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/discovery_config.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/dm_instance.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/files.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/git.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/ifm.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/license.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/pagination.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/ruleset.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/ruleset_library.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/runs.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/user.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/py.typed +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/ruleset_libraries.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/rulesets.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/settings.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/users.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/Makefile +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/client.models.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/client.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/conf.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/contributing.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/history.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/index.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/installation.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/make.bat +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/modules.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/readme.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/docs/usage.rst +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/__init__.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/conftest.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/helpers.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_base.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_connections.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_discovery_configs.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_files.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_ifm.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_license.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_pagination.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_ruleset_library.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_rulesets.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_runs.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_settings.py +0 -0
- {datamasque_python-1.1.0 → datamasque_python-1.1.1}/tests/test_users.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
History
|
|
3
3
|
=======
|
|
4
4
|
|
|
5
|
+
1.1.1 (2026-06-25)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
* Made ``DiscoveryMatch.label`` optional (it is absent for non-sensitive/ignore matches).
|
|
9
|
+
* Added the ``finished_with_warnings`` status to ``AsyncRulesetGenerationTaskStatus``.
|
|
10
|
+
* ``get_db_discovery_result_report`` may now return ``bytes`` (a zip)
|
|
11
|
+
when the server splits a large DB-discovery report,
|
|
12
|
+
and ruleset generation from CSV now detects and forwards zip uploads.
|
|
13
|
+
|
|
5
14
|
1.1.0 (2026-06-24)
|
|
6
15
|
------------------
|
|
7
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datamasque-python
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Official Python client for the DataMasque data-masking API.
|
|
5
5
|
Project-URL: Homepage, https://datamasque.com/
|
|
6
6
|
Project-URL: Documentation, https://datamasque-python.readthedocs.io/
|
|
@@ -98,6 +98,10 @@ class DiscoveryClient(BaseClient):
|
|
|
98
98
|
- A text file handle (e.g. `open(path)`)
|
|
99
99
|
- A binary file handle (e.g. `open(path, 'rb')`)
|
|
100
100
|
|
|
101
|
+
If the content is a zip (for example a split report from `get_db_discovery_result_report()`),
|
|
102
|
+
it is detected by its magic bytes and uploaded as a zip;
|
|
103
|
+
otherwise it is uploaded as CSV.
|
|
104
|
+
|
|
101
105
|
Generation runs asynchronously on the server.
|
|
102
106
|
Poll `get_async_ruleset_generation_task_status` until it returns
|
|
103
107
|
`AsyncRulesetGenerationTaskStatus.finished`,
|
|
@@ -114,14 +118,22 @@ class DiscoveryClient(BaseClient):
|
|
|
114
118
|
else:
|
|
115
119
|
content = csv_content
|
|
116
120
|
|
|
121
|
+
is_zip = False
|
|
122
|
+
if content.seekable():
|
|
123
|
+
is_zip = content.read(4) == b"PK\x03\x04"
|
|
124
|
+
content.seek(0)
|
|
125
|
+
filename = "ruleset.zip" if is_zip else "ruleset.csv"
|
|
126
|
+
content_type = "application/zip" if is_zip else "text/csv"
|
|
127
|
+
|
|
117
128
|
files = [
|
|
118
129
|
UploadFile(
|
|
119
130
|
field_name="csv_or_zip_file",
|
|
120
|
-
filename=
|
|
131
|
+
filename=filename,
|
|
121
132
|
content=content,
|
|
122
|
-
content_type=
|
|
133
|
+
content_type=content_type,
|
|
123
134
|
),
|
|
124
135
|
]
|
|
136
|
+
|
|
125
137
|
self.make_request(
|
|
126
138
|
method="POST",
|
|
127
139
|
path=f"/api/async-generate-ruleset/{connection_id}/from-csv/",
|
|
@@ -252,7 +252,7 @@ class DiscoveryMatch(BaseModel):
|
|
|
252
252
|
|
|
253
253
|
model_config = ConfigDict(extra="allow")
|
|
254
254
|
|
|
255
|
-
label: str
|
|
255
|
+
label: Optional[str] = None
|
|
256
256
|
categories: list[str]
|
|
257
257
|
flagged_by: str
|
|
258
258
|
description: str
|
|
@@ -343,8 +343,8 @@ class FileDiscoveryMatch(BaseModel):
|
|
|
343
343
|
|
|
344
344
|
flagged_by: str
|
|
345
345
|
description: str
|
|
346
|
-
label: Optional[str] = None # Omitted for non-sensitive
|
|
347
|
-
categories: Optional[list[str]] = None
|
|
346
|
+
label: Optional[str] = None # Omitted for non-sensitive matches.
|
|
347
|
+
categories: Optional[list[str]] = None
|
|
348
348
|
hit_ratio: Optional[int] = None # None for metadata matches, percentage 0-100 for IDD matches.
|
|
349
349
|
|
|
350
350
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
|
+
from typing import Union
|
|
3
4
|
|
|
4
5
|
from datamasque.client.base import BaseClient
|
|
5
6
|
from datamasque.client.exceptions import (
|
|
@@ -43,9 +44,12 @@ class RunClient(BaseClient):
|
|
|
43
44
|
response = self.make_request("GET", f"api/runs/{run_id}/run-report/")
|
|
44
45
|
return response.text
|
|
45
46
|
|
|
46
|
-
def get_db_discovery_result_report(self, run_id: RunId, include_selection_column: bool = True) -> str:
|
|
47
|
+
def get_db_discovery_result_report(self, run_id: RunId, include_selection_column: bool = True) -> Union[str, bytes]:
|
|
47
48
|
"""
|
|
48
|
-
Returns the database-discovery result report for the specified run
|
|
49
|
+
Returns the database-discovery result report for the specified run.
|
|
50
|
+
|
|
51
|
+
Returns CSV text (`str`),
|
|
52
|
+
or a zip of numbered CSV parts as `bytes` when the server splits a large report.
|
|
49
53
|
|
|
50
54
|
When `include_selection_column` is true (the default),
|
|
51
55
|
the CSV includes a `selected` column suitable for feeding back into ruleset generation.
|
|
@@ -54,6 +58,9 @@ class RunClient(BaseClient):
|
|
|
54
58
|
url = f"api/runs/{run_id}/db-discovery-results/report/"
|
|
55
59
|
params = None if include_selection_column else {"include_selection_column": "false"}
|
|
56
60
|
response = self.make_request("GET", url, params=params)
|
|
61
|
+
|
|
62
|
+
if response.headers.get("Content-Type", "").startswith("application/zip"):
|
|
63
|
+
return response.content
|
|
57
64
|
return response.text
|
|
58
65
|
|
|
59
66
|
def get_unfinished_runs(self) -> dict[str, UnfinishedRun]:
|
|
@@ -108,6 +108,17 @@ def test_get_db_discovery_result_report(client):
|
|
|
108
108
|
assert result == "db discovery report without selection column"
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def test_get_db_discovery_result_report_returns_zip_bytes_when_split(client):
|
|
112
|
+
run_id = RunId(1)
|
|
113
|
+
zip_bytes = b"PK\x03\x04 split report zip bytes"
|
|
114
|
+
with requests_mock.Mocker() as m:
|
|
115
|
+
url = f"http://test-server/api/runs/{run_id}/db-discovery-results/report/"
|
|
116
|
+
m.get(url, content=zip_bytes, headers={"Content-Type": "application/zip"}, status_code=200)
|
|
117
|
+
result = client.get_db_discovery_result_report(run_id)
|
|
118
|
+
assert result == zip_bytes
|
|
119
|
+
assert isinstance(result, bytes)
|
|
120
|
+
|
|
121
|
+
|
|
111
122
|
def test_poll_async_ruleset_generation(client):
|
|
112
123
|
connection_id = ConnectionId("1")
|
|
113
124
|
with requests_mock.Mocker() as m:
|
|
@@ -463,6 +474,31 @@ def test_start_async_ruleset_generation_from_csv_success(client, csv_content):
|
|
|
463
474
|
assert form_data["csv_or_zip_file"]["content"] == b"schema,table,column,selected\npublic,users,email,true"
|
|
464
475
|
|
|
465
476
|
|
|
477
|
+
@pytest.mark.parametrize(
|
|
478
|
+
"zip_content",
|
|
479
|
+
[
|
|
480
|
+
b"PK\x03\x04 zipped discovery report",
|
|
481
|
+
BytesIO(b"PK\x03\x04 zipped discovery report"),
|
|
482
|
+
],
|
|
483
|
+
ids=["bytes", "BytesIO"],
|
|
484
|
+
)
|
|
485
|
+
def test_start_async_ruleset_generation_from_csv_uploads_zip_as_zip(client, zip_content):
|
|
486
|
+
"""A split report is uploaded with a .zip filename and zip content-type, whether passed as bytes or a binary stream."""
|
|
487
|
+
connection_id = ConnectionId("1")
|
|
488
|
+
|
|
489
|
+
with requests_mock.Mocker() as m:
|
|
490
|
+
m.post(
|
|
491
|
+
f"http://test-server/api/async-generate-ruleset/{connection_id}/from-csv/",
|
|
492
|
+
status_code=201,
|
|
493
|
+
)
|
|
494
|
+
client.start_async_ruleset_generation_from_csv(connection_id, zip_content)
|
|
495
|
+
|
|
496
|
+
form_data = parse_multipart_form(m.last_request)
|
|
497
|
+
assert form_data["csv_or_zip_file"]["filename"] == "ruleset.zip"
|
|
498
|
+
assert form_data["csv_or_zip_file"]["content_type"] == "application/zip"
|
|
499
|
+
assert form_data["csv_or_zip_file"]["content"] == b"PK\x03\x04 zipped discovery report"
|
|
500
|
+
|
|
501
|
+
|
|
466
502
|
def test_start_async_ruleset_generation_from_csv_with_target_size(client):
|
|
467
503
|
"""Test async ruleset generation from CSV with target_size_bytes parameter."""
|
|
468
504
|
connection_id = ConnectionId("1")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/data_selection.py
RENAMED
|
File without changes
|
{datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/discovery_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datamasque_python-1.1.0 → datamasque_python-1.1.1}/datamasque/client/models/ruleset_library.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|