quba-sensitive-data-protection 0.0.8__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.
Files changed (32) hide show
  1. quba_sensitive_data_protection-0.0.8/PKG-INFO +109 -0
  2. quba_sensitive_data_protection-0.0.8/README.md +100 -0
  3. quba_sensitive_data_protection-0.0.8/pyproject.toml +25 -0
  4. quba_sensitive_data_protection-0.0.8/quba_sdp/__init__.py +8 -0
  5. quba_sensitive_data_protection-0.0.8/quba_sdp/api/__init__.py +1 -0
  6. quba_sensitive_data_protection-0.0.8/quba_sdp/api/anonymize_text.py +206 -0
  7. quba_sensitive_data_protection-0.0.8/quba_sdp/api/health_check.py +142 -0
  8. quba_sensitive_data_protection-0.0.8/quba_sdp/api/scan_text.py +194 -0
  9. quba_sensitive_data_protection-0.0.8/quba_sdp/client.py +268 -0
  10. quba_sensitive_data_protection-0.0.8/quba_sdp/errors.py +16 -0
  11. quba_sensitive_data_protection-0.0.8/quba_sdp/models/__init__.py +43 -0
  12. quba_sensitive_data_protection-0.0.8/quba_sdp/models/anonymize_request_body.py +190 -0
  13. quba_sensitive_data_protection-0.0.8/quba_sdp/models/anonymize_response_body.py +117 -0
  14. quba_sensitive_data_protection-0.0.8/quba_sdp/models/encrypt_rule.py +126 -0
  15. quba_sensitive_data_protection-0.0.8/quba_sdp/models/http_validation_error.py +79 -0
  16. quba_sensitive_data_protection-0.0.8/quba_sdp/models/mask_rule.py +156 -0
  17. quba_sensitive_data_protection-0.0.8/quba_sdp/models/model_entity.py +75 -0
  18. quba_sensitive_data_protection-0.0.8/quba_sdp/models/model_result.py +117 -0
  19. quba_sensitive_data_protection-0.0.8/quba_sdp/models/redact_rule.py +115 -0
  20. quba_sensitive_data_protection-0.0.8/quba_sdp/models/regex_entity.py +75 -0
  21. quba_sensitive_data_protection-0.0.8/quba_sdp/models/regex_result.py +108 -0
  22. quba_sensitive_data_protection-0.0.8/quba_sdp/models/replace_rule.py +124 -0
  23. quba_sensitive_data_protection-0.0.8/quba_sdp/models/scan_request_body.py +106 -0
  24. quba_sensitive_data_protection-0.0.8/quba_sdp/models/scan_response_body.py +77 -0
  25. quba_sensitive_data_protection-0.0.8/quba_sdp/models/scan_result.py +86 -0
  26. quba_sensitive_data_protection-0.0.8/quba_sdp/models/sha256_rule.py +115 -0
  27. quba_sensitive_data_protection-0.0.8/quba_sdp/models/sha512_rule.py +115 -0
  28. quba_sensitive_data_protection-0.0.8/quba_sdp/models/text_range.py +78 -0
  29. quba_sensitive_data_protection-0.0.8/quba_sdp/models/validation_error.py +123 -0
  30. quba_sensitive_data_protection-0.0.8/quba_sdp/models/validation_error_context.py +47 -0
  31. quba_sensitive_data_protection-0.0.8/quba_sdp/py.typed +1 -0
  32. quba_sensitive_data_protection-0.0.8/quba_sdp/types.py +54 -0
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.3
2
+ Name: quba-sensitive-data-protection
3
+ Version: 0.0.8
4
+ Summary: A client library for accessing Sensitive Data Protection API
5
+ Requires-Dist: httpx>=0.23.1,<0.29.0
6
+ Requires-Dist: attrs>=22.2.0
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+
10
+ # quba-sensitive-data-protection
11
+
12
+ A client library for accessing Sensitive Data Protection API — a Python client for detecting and de-identifying
13
+ sensitive data (PII) in text: **scan** for entities (emails, names, phone
14
+ numbers, …) and **anonymize** them with configurable rules (redact, mask,
15
+ replace, hash, encrypt).
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install quba-sensitive-data-protection
21
+ # or
22
+ uv add quba-sensitive-data-protection
23
+ ```
24
+
25
+ ## Quickstart
26
+
27
+ ```python
28
+ from quba_sdp import Client, models
29
+ from quba_sdp.api import scan_text, anonymize_text
30
+
31
+ client = Client() # defaults to the production API; pass base_url=... to override
32
+
33
+ # --- scan: locate sensitive entities ---
34
+ resp = scan_text.sync(client=client, body=models.ScanRequestBody(text="Email me at a@b.com"))
35
+ if isinstance(resp, models.ScanResponseBody):
36
+ for r in resp.results:
37
+ print(r.entity_type, r.start, r.end, r.score)
38
+
39
+ # --- anonymize: transform matches ---
40
+ body = models.AnonymizeRequestBody(
41
+ text="Email me at a@b.com",
42
+ rules=[
43
+ models.MaskRule(entities=[models.ModelEntity(value="email")], masking_char="*"),
44
+ ],
45
+ )
46
+ result = anonymize_text.sync(client=client, body=body)
47
+ if isinstance(result, models.AnonymizeResponseBody):
48
+ print(result.text) # 'Email me at ***********'
49
+ ```
50
+
51
+ ## Base URL
52
+
53
+ `Client()` targets the production API by default. Override it for local or
54
+ staging:
55
+
56
+ ```python
57
+ client = Client(base_url="http://localhost:8000")
58
+ ```
59
+
60
+ ## Authentication
61
+
62
+ The production API requires a token — use `AuthenticatedClient` (it shares the
63
+ same default base URL):
64
+
65
+ ```python
66
+ from quba_sdp import AuthenticatedClient
67
+
68
+ client = AuthenticatedClient(token="SuperSecretToken")
69
+ ```
70
+
71
+ ## Calling endpoints
72
+
73
+ Every operation lives in a module under `quba_sdp.api` and exposes four
74
+ functions:
75
+
76
+ | function | blocking | returns |
77
+ | --- | --- | --- |
78
+ | `sync` | ✅ | parsed body, or `None` for an undocumented/error status |
79
+ | `sync_detailed` | ✅ | `Response[...]` with `.status_code`, `.headers`, `.parsed` |
80
+ | `asyncio` | — | like `sync`, awaitable |
81
+ | `asyncio_detailed` | — | like `sync_detailed`, awaitable |
82
+
83
+ `sync` returns a **union** of the success body and any documented error (e.g.
84
+ `HTTPValidationError`), so narrow it before use:
85
+
86
+ ```python
87
+ r = scan_text.sync(client=client, body=...)
88
+ if isinstance(r, models.ScanResponseBody):
89
+ ... # success
90
+ else:
91
+ ... # HTTPValidationError or None
92
+ ```
93
+
94
+ Async variant:
95
+
96
+ ```python
97
+ async with Client() as client:
98
+ resp = await scan_text.asyncio(client=client, body=models.ScanRequestBody(text="..."))
99
+ ```
100
+
101
+ ## Advanced configuration
102
+
103
+ `Client` / `AuthenticatedClient` accept `timeout=`, `verify_ssl=`, `headers=`,
104
+ `cookies=`, and `httpx_args=` for full control of the underlying `httpx` client
105
+ (see the `Client` docstring). Disabling TLS verification is a security risk:
106
+
107
+ ```python
108
+ client = Client(verify_ssl=False) # not recommended
109
+ ```
@@ -0,0 +1,100 @@
1
+ # quba-sensitive-data-protection
2
+
3
+ A client library for accessing Sensitive Data Protection API — a Python client for detecting and de-identifying
4
+ sensitive data (PII) in text: **scan** for entities (emails, names, phone
5
+ numbers, …) and **anonymize** them with configurable rules (redact, mask,
6
+ replace, hash, encrypt).
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install quba-sensitive-data-protection
12
+ # or
13
+ uv add quba-sensitive-data-protection
14
+ ```
15
+
16
+ ## Quickstart
17
+
18
+ ```python
19
+ from quba_sdp import Client, models
20
+ from quba_sdp.api import scan_text, anonymize_text
21
+
22
+ client = Client() # defaults to the production API; pass base_url=... to override
23
+
24
+ # --- scan: locate sensitive entities ---
25
+ resp = scan_text.sync(client=client, body=models.ScanRequestBody(text="Email me at a@b.com"))
26
+ if isinstance(resp, models.ScanResponseBody):
27
+ for r in resp.results:
28
+ print(r.entity_type, r.start, r.end, r.score)
29
+
30
+ # --- anonymize: transform matches ---
31
+ body = models.AnonymizeRequestBody(
32
+ text="Email me at a@b.com",
33
+ rules=[
34
+ models.MaskRule(entities=[models.ModelEntity(value="email")], masking_char="*"),
35
+ ],
36
+ )
37
+ result = anonymize_text.sync(client=client, body=body)
38
+ if isinstance(result, models.AnonymizeResponseBody):
39
+ print(result.text) # 'Email me at ***********'
40
+ ```
41
+
42
+ ## Base URL
43
+
44
+ `Client()` targets the production API by default. Override it for local or
45
+ staging:
46
+
47
+ ```python
48
+ client = Client(base_url="http://localhost:8000")
49
+ ```
50
+
51
+ ## Authentication
52
+
53
+ The production API requires a token — use `AuthenticatedClient` (it shares the
54
+ same default base URL):
55
+
56
+ ```python
57
+ from quba_sdp import AuthenticatedClient
58
+
59
+ client = AuthenticatedClient(token="SuperSecretToken")
60
+ ```
61
+
62
+ ## Calling endpoints
63
+
64
+ Every operation lives in a module under `quba_sdp.api` and exposes four
65
+ functions:
66
+
67
+ | function | blocking | returns |
68
+ | --- | --- | --- |
69
+ | `sync` | ✅ | parsed body, or `None` for an undocumented/error status |
70
+ | `sync_detailed` | ✅ | `Response[...]` with `.status_code`, `.headers`, `.parsed` |
71
+ | `asyncio` | — | like `sync`, awaitable |
72
+ | `asyncio_detailed` | — | like `sync_detailed`, awaitable |
73
+
74
+ `sync` returns a **union** of the success body and any documented error (e.g.
75
+ `HTTPValidationError`), so narrow it before use:
76
+
77
+ ```python
78
+ r = scan_text.sync(client=client, body=...)
79
+ if isinstance(r, models.ScanResponseBody):
80
+ ... # success
81
+ else:
82
+ ... # HTTPValidationError or None
83
+ ```
84
+
85
+ Async variant:
86
+
87
+ ```python
88
+ async with Client() as client:
89
+ resp = await scan_text.asyncio(client=client, body=models.ScanRequestBody(text="..."))
90
+ ```
91
+
92
+ ## Advanced configuration
93
+
94
+ `Client` / `AuthenticatedClient` accept `timeout=`, `verify_ssl=`, `headers=`,
95
+ `cookies=`, and `httpx_args=` for full control of the underlying `httpx` client
96
+ (see the `Client` docstring). Disabling TLS verification is a security risk:
97
+
98
+ ```python
99
+ client = Client(verify_ssl=False) # not recommended
100
+ ```
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "quba-sensitive-data-protection"
3
+ version = "0.0.8"
4
+ description = "A client library for accessing Sensitive Data Protection API"
5
+ authors = []
6
+ requires-python = ">=3.11"
7
+ readme = "README.md"
8
+ dependencies = [
9
+ "httpx>=0.23.1,<0.29.0",
10
+ "attrs>=22.2.0",
11
+ ]
12
+
13
+ [tool.uv.build-backend]
14
+ module-name = "quba_sdp"
15
+ module-root = ""
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.11.0,<0.12.0"]
19
+ build-backend = "uv_build"
20
+
21
+ [tool.ruff]
22
+ line-length = 120
23
+
24
+ [tool.ruff.lint]
25
+ select = ["F", "I", "UP"]
@@ -0,0 +1,8 @@
1
+ """A client library for accessing Sensitive Data Protection API"""
2
+
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """Contains methods for accessing the API"""
@@ -0,0 +1,206 @@
1
+ from http import HTTPStatus
2
+ from typing import Any
3
+
4
+ import httpx
5
+
6
+ from .. import errors
7
+ from ..client import AuthenticatedClient, Client
8
+ from ..models.anonymize_request_body import AnonymizeRequestBody
9
+ from ..models.anonymize_response_body import AnonymizeResponseBody
10
+ from ..models.http_validation_error import HTTPValidationError
11
+ from ..types import Response
12
+
13
+
14
+ def _get_kwargs(
15
+ *,
16
+ body: AnonymizeRequestBody,
17
+ ) -> dict[str, Any]:
18
+ headers: dict[str, Any] = {}
19
+
20
+ _kwargs: dict[str, Any] = {
21
+ "method": "post",
22
+ "url": "/anonymize",
23
+ }
24
+
25
+ _kwargs["json"] = body.to_dict()
26
+
27
+ headers["Content-Type"] = "application/json"
28
+
29
+ _kwargs["headers"] = headers
30
+ return _kwargs
31
+
32
+
33
+ def _parse_response(
34
+ *, client: AuthenticatedClient | Client, response: httpx.Response
35
+ ) -> AnonymizeResponseBody | HTTPValidationError | None:
36
+ if response.status_code == 200:
37
+ response_200 = AnonymizeResponseBody.from_dict(response.json())
38
+
39
+ return response_200
40
+
41
+ if response.status_code == 422:
42
+ response_422 = HTTPValidationError.from_dict(response.json())
43
+
44
+ return response_422
45
+
46
+ if client.raise_on_unexpected_status:
47
+ raise errors.UnexpectedStatus(response.status_code, response.content)
48
+ else:
49
+ return None
50
+
51
+
52
+ def _build_response(
53
+ *, client: AuthenticatedClient | Client, response: httpx.Response
54
+ ) -> Response[AnonymizeResponseBody | HTTPValidationError]:
55
+ return Response(
56
+ status_code=HTTPStatus(response.status_code),
57
+ content=response.content,
58
+ headers=response.headers,
59
+ parsed=_parse_response(client=client, response=response),
60
+ )
61
+
62
+
63
+ def sync_detailed(
64
+ *,
65
+ client: AuthenticatedClient | Client,
66
+ body: AnonymizeRequestBody,
67
+ ) -> Response[AnonymizeResponseBody | HTTPValidationError]:
68
+ """Anonymize Text
69
+
70
+ Anonymize sensitive data in text.
71
+
72
+ Scans the request text for the entities targeted by each supplied rule and
73
+ applies that rule's operation (replace, redact, mask, hash, or encrypt) to
74
+ every match. Returns the anonymized text together with a record of each
75
+ transformation, including the original and resulting character ranges.
76
+
77
+ Args:
78
+ body (AnonymizeRequestBody): Request body for the anonymize endpoint.
79
+
80
+ Carries the text to anonymize and the list of de-identification rules to
81
+ apply. See ``AnonymizationCommand`` for field-level details.
82
+
83
+ Raises:
84
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
85
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
86
+
87
+ Returns:
88
+ Response[AnonymizeResponseBody | HTTPValidationError]
89
+ """
90
+
91
+ kwargs = _get_kwargs(
92
+ body=body,
93
+ )
94
+
95
+ response = client.get_httpx_client().request(
96
+ **kwargs,
97
+ )
98
+
99
+ return _build_response(client=client, response=response)
100
+
101
+
102
+ def sync(
103
+ *,
104
+ client: AuthenticatedClient | Client,
105
+ body: AnonymizeRequestBody,
106
+ ) -> AnonymizeResponseBody | HTTPValidationError | None:
107
+ """Anonymize Text
108
+
109
+ Anonymize sensitive data in text.
110
+
111
+ Scans the request text for the entities targeted by each supplied rule and
112
+ applies that rule's operation (replace, redact, mask, hash, or encrypt) to
113
+ every match. Returns the anonymized text together with a record of each
114
+ transformation, including the original and resulting character ranges.
115
+
116
+ Args:
117
+ body (AnonymizeRequestBody): Request body for the anonymize endpoint.
118
+
119
+ Carries the text to anonymize and the list of de-identification rules to
120
+ apply. See ``AnonymizationCommand`` for field-level details.
121
+
122
+ Raises:
123
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
124
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
125
+
126
+ Returns:
127
+ AnonymizeResponseBody | HTTPValidationError
128
+ """
129
+
130
+ return sync_detailed(
131
+ client=client,
132
+ body=body,
133
+ ).parsed
134
+
135
+
136
+ async def asyncio_detailed(
137
+ *,
138
+ client: AuthenticatedClient | Client,
139
+ body: AnonymizeRequestBody,
140
+ ) -> Response[AnonymizeResponseBody | HTTPValidationError]:
141
+ """Anonymize Text
142
+
143
+ Anonymize sensitive data in text.
144
+
145
+ Scans the request text for the entities targeted by each supplied rule and
146
+ applies that rule's operation (replace, redact, mask, hash, or encrypt) to
147
+ every match. Returns the anonymized text together with a record of each
148
+ transformation, including the original and resulting character ranges.
149
+
150
+ Args:
151
+ body (AnonymizeRequestBody): Request body for the anonymize endpoint.
152
+
153
+ Carries the text to anonymize and the list of de-identification rules to
154
+ apply. See ``AnonymizationCommand`` for field-level details.
155
+
156
+ Raises:
157
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
158
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
159
+
160
+ Returns:
161
+ Response[AnonymizeResponseBody | HTTPValidationError]
162
+ """
163
+
164
+ kwargs = _get_kwargs(
165
+ body=body,
166
+ )
167
+
168
+ response = await client.get_async_httpx_client().request(**kwargs)
169
+
170
+ return _build_response(client=client, response=response)
171
+
172
+
173
+ async def asyncio(
174
+ *,
175
+ client: AuthenticatedClient | Client,
176
+ body: AnonymizeRequestBody,
177
+ ) -> AnonymizeResponseBody | HTTPValidationError | None:
178
+ """Anonymize Text
179
+
180
+ Anonymize sensitive data in text.
181
+
182
+ Scans the request text for the entities targeted by each supplied rule and
183
+ applies that rule's operation (replace, redact, mask, hash, or encrypt) to
184
+ every match. Returns the anonymized text together with a record of each
185
+ transformation, including the original and resulting character ranges.
186
+
187
+ Args:
188
+ body (AnonymizeRequestBody): Request body for the anonymize endpoint.
189
+
190
+ Carries the text to anonymize and the list of de-identification rules to
191
+ apply. See ``AnonymizationCommand`` for field-level details.
192
+
193
+ Raises:
194
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
195
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
196
+
197
+ Returns:
198
+ AnonymizeResponseBody | HTTPValidationError
199
+ """
200
+
201
+ return (
202
+ await asyncio_detailed(
203
+ client=client,
204
+ body=body,
205
+ )
206
+ ).parsed
@@ -0,0 +1,142 @@
1
+ from http import HTTPStatus
2
+ from typing import Any, cast
3
+
4
+ import httpx
5
+
6
+ from .. import errors
7
+ from ..client import AuthenticatedClient, Client
8
+ from ..types import Response
9
+
10
+
11
+ def _get_kwargs() -> dict[str, Any]:
12
+
13
+ _kwargs: dict[str, Any] = {
14
+ "method": "get",
15
+ "url": "/health",
16
+ }
17
+
18
+ return _kwargs
19
+
20
+
21
+ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> str | None:
22
+ if response.status_code == 200:
23
+ response_200 = cast(str, response.json())
24
+ return response_200
25
+
26
+ if client.raise_on_unexpected_status:
27
+ raise errors.UnexpectedStatus(response.status_code, response.content)
28
+ else:
29
+ return None
30
+
31
+
32
+ def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[str]:
33
+ return Response(
34
+ status_code=HTTPStatus(response.status_code),
35
+ content=response.content,
36
+ headers=response.headers,
37
+ parsed=_parse_response(client=client, response=response),
38
+ )
39
+
40
+
41
+ def sync_detailed(
42
+ *,
43
+ client: AuthenticatedClient | Client,
44
+ ) -> Response[str]:
45
+ """Health
46
+
47
+ Health check.
48
+
49
+ Returns a plain-text message confirming the service is running. Use this as
50
+ a liveness/readiness probe.
51
+
52
+ Raises:
53
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
54
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
55
+
56
+ Returns:
57
+ Response[str]
58
+ """
59
+
60
+ kwargs = _get_kwargs()
61
+
62
+ response = client.get_httpx_client().request(
63
+ **kwargs,
64
+ )
65
+
66
+ return _build_response(client=client, response=response)
67
+
68
+
69
+ def sync(
70
+ *,
71
+ client: AuthenticatedClient | Client,
72
+ ) -> str | None:
73
+ """Health
74
+
75
+ Health check.
76
+
77
+ Returns a plain-text message confirming the service is running. Use this as
78
+ a liveness/readiness probe.
79
+
80
+ Raises:
81
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
82
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
83
+
84
+ Returns:
85
+ str
86
+ """
87
+
88
+ return sync_detailed(
89
+ client=client,
90
+ ).parsed
91
+
92
+
93
+ async def asyncio_detailed(
94
+ *,
95
+ client: AuthenticatedClient | Client,
96
+ ) -> Response[str]:
97
+ """Health
98
+
99
+ Health check.
100
+
101
+ Returns a plain-text message confirming the service is running. Use this as
102
+ a liveness/readiness probe.
103
+
104
+ Raises:
105
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
106
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
107
+
108
+ Returns:
109
+ Response[str]
110
+ """
111
+
112
+ kwargs = _get_kwargs()
113
+
114
+ response = await client.get_async_httpx_client().request(**kwargs)
115
+
116
+ return _build_response(client=client, response=response)
117
+
118
+
119
+ async def asyncio(
120
+ *,
121
+ client: AuthenticatedClient | Client,
122
+ ) -> str | None:
123
+ """Health
124
+
125
+ Health check.
126
+
127
+ Returns a plain-text message confirming the service is running. Use this as
128
+ a liveness/readiness probe.
129
+
130
+ Raises:
131
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
132
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
133
+
134
+ Returns:
135
+ str
136
+ """
137
+
138
+ return (
139
+ await asyncio_detailed(
140
+ client=client,
141
+ )
142
+ ).parsed