datamasque-python 1.1.7__tar.gz → 1.2.0__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 (87) hide show
  1. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/HISTORY.rst +22 -0
  2. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/PKG-INFO +1 -1
  3. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/__init__.py +72 -0
  4. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/discovery_config_libraries.py +14 -26
  5. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/dmclient.py +2 -0
  6. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/discovery.py +8 -0
  7. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/discovery_config_library.py +2 -4
  8. datamasque_python-1.2.0/datamasque/client/models/safe_data_preview.py +282 -0
  9. datamasque_python-1.2.0/datamasque/client/models/table_reference.py +101 -0
  10. datamasque_python-1.2.0/datamasque/client/table_references.py +144 -0
  11. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/pyproject.toml +1 -1
  12. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/setup.cfg +1 -1
  13. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_discovery.py +20 -0
  14. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_discovery_config_libraries.py +12 -142
  15. datamasque_python-1.2.0/tests/test_safe_data_preview.py +178 -0
  16. datamasque_python-1.2.0/tests/test_table_references.py +502 -0
  17. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/uv.lock +1 -1
  18. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/.editorconfig +0 -0
  19. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/.github/workflows/ci.yml +0 -0
  20. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/.github/workflows/release-testpypi.yml +0 -0
  21. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/.github/workflows/release.yml +0 -0
  22. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/.gitignore +0 -0
  23. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/.readthedocs.yaml +0 -0
  24. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/CONTRIBUTING.rst +0 -0
  25. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/LICENSE +0 -0
  26. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/MANIFEST.in +0 -0
  27. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/Makefile +0 -0
  28. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/NOTICE +0 -0
  29. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/README.rst +0 -0
  30. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/base.py +0 -0
  31. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/connections.py +0 -0
  32. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/discovery.py +0 -0
  33. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/discovery_configs.py +0 -0
  34. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/exceptions.py +0 -0
  35. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/files.py +0 -0
  36. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/ifm.py +0 -0
  37. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/license.py +0 -0
  38. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/__init__.py +0 -0
  39. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/connection.py +0 -0
  40. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/data_selection.py +0 -0
  41. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/discovery_config.py +0 -0
  42. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/dm_instance.py +0 -0
  43. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/files.py +0 -0
  44. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/git.py +0 -0
  45. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/ifm.py +0 -0
  46. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/license.py +0 -0
  47. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/pagination.py +0 -0
  48. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/ruleset.py +0 -0
  49. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/ruleset_library.py +0 -0
  50. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/runs.py +0 -0
  51. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/status.py +0 -0
  52. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/models/user.py +0 -0
  53. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/py.typed +0 -0
  54. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/ruleset_libraries.py +0 -0
  55. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/rulesets.py +0 -0
  56. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/runs.py +0 -0
  57. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/settings.py +0 -0
  58. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/spcs.py +0 -0
  59. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/datamasque/client/users.py +0 -0
  60. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/Makefile +0 -0
  61. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/client.models.rst +0 -0
  62. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/client.rst +0 -0
  63. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/conf.py +0 -0
  64. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/contributing.rst +0 -0
  65. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/history.rst +0 -0
  66. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/index.rst +0 -0
  67. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/installation.rst +0 -0
  68. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/make.bat +0 -0
  69. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/modules.rst +0 -0
  70. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/readme.rst +0 -0
  71. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/docs/usage.rst +0 -0
  72. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/__init__.py +0 -0
  73. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/conftest.py +0 -0
  74. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/helpers.py +0 -0
  75. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_base.py +0 -0
  76. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_connections.py +0 -0
  77. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_discovery_configs.py +0 -0
  78. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_files.py +0 -0
  79. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_ifm.py +0 -0
  80. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_license.py +0 -0
  81. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_pagination.py +0 -0
  82. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_ruleset_library.py +0 -0
  83. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_rulesets.py +0 -0
  84. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_runs.py +0 -0
  85. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_settings.py +0 -0
  86. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_spcs.py +0 -0
  87. {datamasque_python-1.1.7 → datamasque_python-1.2.0}/tests/test_users.py +0 -0
@@ -2,6 +2,28 @@
2
2
  History
3
3
  =======
4
4
 
5
+ 1.2.0 (2026-07-27)
6
+ ------------------
7
+
8
+ * Made discovery config libraries untyped, matching the DataMasque 3.26.14 server. A single
9
+ library is now identified by ``(namespace, name)`` and may be imported by both database and
10
+ file discovery configs. **Breaking:** removed the ``config_type`` field from
11
+ ``DiscoveryConfigLibrary`` and the ``config_type`` argument from
12
+ ``get_discovery_config_library_by_name``, ``create_or_update_discovery_config_library``, and
13
+ ``delete_discovery_config_library_by_name_if_exists``. Requires a 3.26.14 or later server.
14
+
15
+ 1.1.8 (2026-07-27)
16
+ ------------------
17
+
18
+ * Added typed Safe Data Preview support:
19
+
20
+ * ``safe_data_preview`` on ``InDataDiscoveryConfig`` (via ``SafeDataPreviewOptions``) to configure or disable it.
21
+ * ``safe_data_preview`` on schema-discovery result columns and file-discovery locators, typed by ``kind``.
22
+
23
+ * Added ``row_count`` to ``TableConstraints`` in schema-discovery ``table_metadata``.
24
+ * Added table-reference management APIs (``list_table_references``, ``create_table_reference``, and friends),
25
+ along with the ``TableReference`` and ``TableReferenceOptions`` models.
26
+
5
27
  1.1.7 (2026-07-14)
6
28
  ------------------
7
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datamasque-python
3
- Version: 1.1.7
3
+ Version: 1.2.0
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/
@@ -116,6 +116,38 @@ from datamasque.client.models.runs import (
116
116
  RunInfo,
117
117
  UnfinishedRun,
118
118
  )
119
+ from datamasque.client.models.safe_data_preview import (
120
+ BooleanPreview,
121
+ BooleanStatistics,
122
+ ColumnKind,
123
+ ColumnPreview,
124
+ CommonStatistics,
125
+ FirstCharsStatistics,
126
+ LengthEntry,
127
+ LengthsStatistics,
128
+ MaskedFormEntry,
129
+ NumericBin,
130
+ NumericHistograms,
131
+ NumericPreview,
132
+ NumericStatistics,
133
+ NumericSummaries,
134
+ NumericTemporalDisclosureLevel,
135
+ PatternComposition,
136
+ PatternEntry,
137
+ PatternsStatistics,
138
+ SafeDataPreview,
139
+ SafeDataPreviewOptions,
140
+ StringDisclosureLevel,
141
+ StringPreview,
142
+ StringStatistics,
143
+ TemporalBin,
144
+ TemporalHistograms,
145
+ TemporalPreview,
146
+ TemporalStatistics,
147
+ TemporalSummaries,
148
+ UnsupportedPreview,
149
+ UnsupportedStatistics,
150
+ )
119
151
  from datamasque.client.models.status import (
120
152
  AsyncRulesetGenerationTaskStatus,
121
153
  MaskingRunStatus,
@@ -123,6 +155,12 @@ from datamasque.client.models.status import (
123
155
  ValidationErrorType,
124
156
  ValidationStatus,
125
157
  )
158
+ from datamasque.client.models.table_reference import (
159
+ TableReference,
160
+ TableReferenceFormat,
161
+ TableReferenceId,
162
+ TableReferenceOptions,
163
+ )
126
164
  from datamasque.client.models.user import User, UserId, UserRole
127
165
 
128
166
  __version__ = version("datamasque-python")
@@ -131,6 +169,11 @@ __all__ = [
131
169
  "AsyncRulesetGenerationInProgressError",
132
170
  "AsyncRulesetGenerationTaskStatus",
133
171
  "AzureConnectionConfig",
172
+ "BooleanPreview",
173
+ "BooleanStatistics",
174
+ "ColumnKind",
175
+ "ColumnPreview",
176
+ "CommonStatistics",
134
177
  "ConnectionConfig",
135
178
  "ConnectionId",
136
179
  "ConstraintColumns",
@@ -171,6 +214,7 @@ __all__ = [
171
214
  "FileId",
172
215
  "FileOrContent",
173
216
  "FileRulesetGenerationRequest",
217
+ "FirstCharsStatistics",
174
218
  "ForeignKeyRef",
175
219
  "GitSnapshot",
176
220
  "HashColumnsTableConfig",
@@ -186,16 +230,28 @@ __all__ = [
186
230
  "InvalidLibraryError",
187
231
  "InvalidRulesetError",
188
232
  "JsonPath",
233
+ "LengthEntry",
234
+ "LengthsStatistics",
189
235
  "LicenseInfo",
190
236
  "Locator",
191
237
  "MaskType",
238
+ "MaskedFormEntry",
192
239
  "MaskingRunOptions",
193
240
  "MaskingRunRequest",
194
241
  "MaskingRunStatus",
195
242
  "MongoConnectionConfig",
196
243
  "MountedShareConnectionConfig",
197
244
  "MssqlLinkedServerConnectionConfig",
245
+ "NumericBin",
246
+ "NumericHistograms",
247
+ "NumericPreview",
248
+ "NumericStatistics",
249
+ "NumericSummaries",
250
+ "NumericTemporalDisclosureLevel",
198
251
  "OracleWalletFile",
252
+ "PatternComposition",
253
+ "PatternEntry",
254
+ "PatternsStatistics",
199
255
  "ReferencingForeignKey",
200
256
  "Ruleset",
201
257
  "RulesetGenerationRequest",
@@ -213,6 +269,8 @@ __all__ = [
213
269
  "RunInfo",
214
270
  "RunNotCancellableError",
215
271
  "S3ConnectionConfig",
272
+ "SafeDataPreview",
273
+ "SafeDataPreviewOptions",
216
274
  "SchemaDiscoveryColumn",
217
275
  "SchemaDiscoveryFromConfigRequest",
218
276
  "SchemaDiscoveryPage",
@@ -228,9 +286,23 @@ __all__ = [
228
286
  "SseConfig",
229
287
  "SseSelection",
230
288
  "SslZipFile",
289
+ "StringDisclosureLevel",
290
+ "StringPreview",
291
+ "StringStatistics",
231
292
  "SwitchableLicenseMetadata",
232
293
  "TableConstraints",
294
+ "TableReference",
295
+ "TableReferenceFormat",
296
+ "TableReferenceId",
297
+ "TableReferenceOptions",
298
+ "TemporalBin",
299
+ "TemporalHistograms",
300
+ "TemporalPreview",
301
+ "TemporalStatistics",
302
+ "TemporalSummaries",
233
303
  "UnfinishedRun",
304
+ "UnsupportedPreview",
305
+ "UnsupportedStatistics",
234
306
  "User",
235
307
  "UserId",
236
308
  "UserRole",
@@ -3,7 +3,6 @@ from typing import Optional
3
3
 
4
4
  from datamasque.client.base import BaseClient
5
5
  from datamasque.client.exceptions import DataMasqueApiError
6
- from datamasque.client.models.discovery_config import DiscoveryConfigType
7
6
  from datamasque.client.models.discovery_config_library import DiscoveryConfigLibrary, DiscoveryConfigLibraryId
8
7
 
9
8
  logger = logging.getLogger(__name__)
@@ -31,14 +30,12 @@ class DiscoveryConfigLibraryClient(BaseClient):
31
30
  response = self.make_request("GET", f"/api/discovery/config-libraries/{library_id}/")
32
31
  return DiscoveryConfigLibrary.model_validate(response.json())
33
32
 
34
- def _get_discovery_config_library_id_by_name(
35
- self, name: str, config_type: DiscoveryConfigType, namespace: str
36
- ) -> Optional[DiscoveryConfigLibraryId]:
33
+ def _get_discovery_config_library_id_by_name(self, name: str, namespace: str) -> Optional[DiscoveryConfigLibraryId]:
37
34
  """
38
- Returns the ID of the library with the given name, type, and namespace, or `None` if there is none.
35
+ Returns the ID of the library with the given name and namespace, or `None` if there is none.
39
36
 
40
37
  The listing is filtered by name to keep the response small;
41
- type and namespace are matched client-side,
38
+ the namespace is matched client-side,
42
39
  since the API's namespace filter cannot select the default (empty) namespace.
43
40
  """
44
41
 
@@ -48,11 +45,7 @@ class DiscoveryConfigLibraryClient(BaseClient):
48
45
  params={"name_exact": name},
49
46
  )
50
47
  entries = [DiscoveryConfigLibrary.model_validate(item) for item in response.json()]
51
- matches = [
52
- entry
53
- for entry in entries
54
- if entry.name == name and entry.namespace == namespace and entry.config_type is config_type
55
- ]
48
+ matches = [entry for entry in entries if entry.name == name and entry.namespace == namespace]
56
49
  if not matches:
57
50
  return None
58
51
 
@@ -64,18 +57,15 @@ class DiscoveryConfigLibraryClient(BaseClient):
64
57
 
65
58
  return matches[0].id
66
59
 
67
- def get_discovery_config_library_by_name(
68
- self, name: str, config_type: DiscoveryConfigType, namespace: str = ""
69
- ) -> Optional[DiscoveryConfigLibrary]:
60
+ def get_discovery_config_library_by_name(self, name: str, namespace: str = "") -> Optional[DiscoveryConfigLibrary]:
70
61
  """
71
- Looks for a discovery config library matching the given name, type, and namespace (case-sensitive, exact match).
62
+ Looks for a discovery config library matching the given name and namespace (case-sensitive, exact match).
72
63
 
73
- Library names are unique per type within a namespace,
74
- so a type is required to identify a single library.
64
+ Library names are unique within a namespace.
75
65
  Returns it (with full YAML content) if found, otherwise `None`.
76
66
  """
77
67
 
78
- library_id = self._get_discovery_config_library_id_by_name(name, config_type, namespace)
68
+ library_id = self._get_discovery_config_library_id_by_name(name, namespace)
79
69
  if library_id is None:
80
70
  return None
81
71
 
@@ -106,7 +96,6 @@ class DiscoveryConfigLibraryClient(BaseClient):
106
96
 
107
97
  The library must have its `id` set (i.e., it must have been previously created or retrieved from the server)
108
98
  and its `yaml` content present.
109
- A library's `config_type` is fixed at creation and cannot be changed by an update.
110
99
  """
111
100
 
112
101
  if library.id is None:
@@ -129,12 +118,12 @@ class DiscoveryConfigLibraryClient(BaseClient):
129
118
 
130
119
  def create_or_update_discovery_config_library(self, library: DiscoveryConfigLibrary) -> DiscoveryConfigLibrary:
131
120
  """
132
- Creates the library, or updates the existing one with the same name, namespace, and config type.
121
+ Creates the library, or updates the existing one with the same name and namespace.
133
122
 
134
123
  Sets the library's `id` property.
135
124
  """
136
125
 
137
- library_id = self._get_discovery_config_library_id_by_name(library.name, library.config_type, library.namespace)
126
+ library_id = self._get_discovery_config_library_id_by_name(library.name, library.namespace)
138
127
  if library_id is not None:
139
128
  library.id = library_id
140
129
  return self.update_discovery_config_library(library)
@@ -157,16 +146,15 @@ class DiscoveryConfigLibraryClient(BaseClient):
157
146
  self._delete_if_exists(f"/api/discovery/config-libraries/{library_id}/", params=params)
158
147
 
159
148
  def delete_discovery_config_library_by_name_if_exists(
160
- self, name: str, config_type: DiscoveryConfigType, namespace: str = "", *, force: bool = False
149
+ self, name: str, namespace: str = "", *, force: bool = False
161
150
  ) -> None:
162
151
  """
163
- Deletes the discovery config library with the given name, type, and namespace.
152
+ Deletes the discovery config library with the given name and namespace.
164
153
 
165
- Library names are unique per type within a namespace,
166
- so a type is required to identify a single library.
154
+ Library names are unique within a namespace.
167
155
  No-op if no such library exists.
168
156
  """
169
157
 
170
- library_id = self._get_discovery_config_library_id_by_name(name, config_type, namespace)
158
+ library_id = self._get_discovery_config_library_id_by_name(name, namespace)
171
159
  if library_id is not None:
172
160
  self.delete_discovery_config_library_by_id_if_exists(library_id, force=force)
@@ -9,6 +9,7 @@ from datamasque.client.ruleset_libraries import RulesetLibraryClient
9
9
  from datamasque.client.rulesets import RulesetClient
10
10
  from datamasque.client.runs import RunClient
11
11
  from datamasque.client.settings import SettingsClient
12
+ from datamasque.client.table_references import TableReferenceClient
12
13
  from datamasque.client.users import UserClient
13
14
 
14
15
  __all__ = ["DataMasqueClient", "FileOrContent", "UploadFile"]
@@ -24,6 +25,7 @@ class DataMasqueClient(
24
25
  DiscoveryClient,
25
26
  DiscoveryConfigClient,
26
27
  DiscoveryConfigLibraryClient,
28
+ TableReferenceClient,
27
29
  UserClient,
28
30
  SettingsClient,
29
31
  ):
@@ -10,6 +10,10 @@ from datamasque.client.models.data_selection import HashColumnsTableConfig, Loca
10
10
  from datamasque.client.models.discovery_config import DiscoveryConfig, DiscoveryConfigId, unwrap_discovery_config_id
11
11
  from datamasque.client.models.pagination import Page
12
12
  from datamasque.client.models.runs import RunConnectionRef
13
+ from datamasque.client.models.safe_data_preview import (
14
+ SafeDataPreview,
15
+ SafeDataPreviewOptions,
16
+ )
13
17
 
14
18
 
15
19
  class InDataDiscoveryRule(BaseModel):
@@ -32,6 +36,7 @@ class InDataDiscoveryConfig(BaseModel):
32
36
  non_sensitive_rules: Optional[list[InDataDiscoveryRule]] = None
33
37
  ignore_rules: Optional[list[InDataDiscoveryRule]] = None
34
38
  force: Optional[bool] = None
39
+ safe_data_preview: Optional[SafeDataPreviewOptions] = None
35
40
 
36
41
 
37
42
  class SchemaDiscoveryRequest(BaseModel):
@@ -294,6 +299,7 @@ class SchemaDiscoveryColumn(BaseModel):
294
299
  unique_index_names: list[str]
295
300
  referencing_foreign_keys: list[ReferencingForeignKey]
296
301
  constraint: str # Primary or Unique, or empty string if column does not participate in a PK/UK
302
+ safe_data_preview: Optional[SafeDataPreview] = None
297
303
 
298
304
 
299
305
  class SchemaDiscoveryResult(BaseModel):
@@ -324,6 +330,7 @@ class TableConstraints(BaseModel):
324
330
  primary_keys: Optional[list[ConstraintColumns]] = None
325
331
  unique_keys: Optional[list[ConstraintColumns]] = None
326
332
  foreign_keys: Optional[list[ConstraintColumns]] = None
333
+ row_count: Optional[int] = None
327
334
 
328
335
 
329
336
  class SchemaDiscoveryPage(Page[SchemaDiscoveryResult]):
@@ -356,6 +363,7 @@ class FileDiscoveryLocatorResult(BaseModel):
356
363
  locator: Locator
357
364
  matches: list[FileDiscoveryMatch]
358
365
  data_types: list[str]
366
+ safe_data_preview: Optional[SafeDataPreview] = None
359
367
 
360
368
 
361
369
  class FileDiscoveryFile(BaseModel):
@@ -3,7 +3,6 @@ from typing import NewType, Optional
3
3
 
4
4
  from pydantic import BaseModel, ConfigDict, Field
5
5
 
6
- from datamasque.client.models.discovery_config import DiscoveryConfigType
7
6
  from datamasque.client.models.status import ValidationStatus
8
7
 
9
8
  DiscoveryConfigLibraryId = NewType("DiscoveryConfigLibraryId", str)
@@ -13,14 +12,13 @@ class DiscoveryConfigLibrary(BaseModel):
13
12
  """
14
13
  Represents a named, namespaced, persisted YAML discovery config library.
15
14
 
16
- Library names are unique per config type within a namespace,
17
- so a database and a file library may share a name.
15
+ A library is untyped: the same library may be imported by both database and
16
+ file discovery configs, and its name is unique within a namespace.
18
17
  """
19
18
 
20
19
  model_config = ConfigDict(extra="allow", populate_by_name=True)
21
20
 
22
21
  name: str
23
- config_type: DiscoveryConfigType
24
22
  namespace: str = ""
25
23
  yaml: Optional[str] = Field(default=None, alias="config_yaml")
26
24
  # Server-populated read-only fields, excluded from request bodies.
@@ -0,0 +1,282 @@
1
+ """Typed request and response shapes for Safe Data Preview, part of In-Data Discovery."""
2
+
3
+ from enum import Enum
4
+ from typing import Annotated, Literal, Optional, Union
5
+
6
+ from pydantic import (
7
+ BaseModel,
8
+ ConfigDict,
9
+ Field,
10
+ )
11
+
12
+
13
+ class StringDisclosureLevel(Enum):
14
+ """How much detail a string Safe Data Preview reveals."""
15
+
16
+ lengths = "lengths"
17
+ patterns = "patterns"
18
+ first_chars = "first_chars"
19
+
20
+
21
+ class NumericTemporalDisclosureLevel(Enum):
22
+ """How much detail a numeric or temporal Safe Data Preview reveals."""
23
+
24
+ summaries = "summaries"
25
+ histograms = "histograms"
26
+
27
+
28
+ class SafeDataPreviewOptions(BaseModel):
29
+ """Configures whether Safe Data Preview runs and how much detail each column reveals."""
30
+
31
+ model_config = ConfigDict(extra="forbid")
32
+
33
+ enabled: Optional[bool] = None
34
+ min_group_size: Optional[int] = None
35
+ string_level: Optional[StringDisclosureLevel] = None
36
+ numeric_level: Optional[NumericTemporalDisclosureLevel] = None
37
+ temporal_level: Optional[NumericTemporalDisclosureLevel] = None
38
+
39
+
40
+ class ColumnKind(str, Enum):
41
+ """Which shape a column's Safe Data Preview takes."""
42
+
43
+ boolean = "boolean"
44
+ numeric = "numeric"
45
+ string = "string"
46
+ temporal = "temporal"
47
+ unsupported = "unsupported"
48
+
49
+
50
+ class CommonStatistics(BaseModel):
51
+ """Row counts shared by every preview kind."""
52
+
53
+ model_config = ConfigDict(extra="allow")
54
+
55
+ count_row: int
56
+ count_null: int
57
+ count_distinct: Optional[int] = None
58
+
59
+
60
+ class LengthEntry(BaseModel):
61
+ """A value length and how many sampled rows had it."""
62
+
63
+ model_config = ConfigDict(extra="allow")
64
+
65
+ length: int
66
+ count: int
67
+
68
+
69
+ class LengthsStatistics(BaseModel):
70
+ """Distribution of value lengths."""
71
+
72
+ model_config = ConfigDict(extra="allow")
73
+
74
+ min: int
75
+ max: int
76
+ mean: float
77
+ median: float
78
+ most_common: list[LengthEntry]
79
+
80
+
81
+ class PatternEntry(BaseModel):
82
+ """A masked character pattern and how many sampled rows matched it."""
83
+
84
+ model_config = ConfigDict(extra="allow")
85
+
86
+ pattern: str
87
+ count: int
88
+
89
+
90
+ class PatternComposition(BaseModel):
91
+ """The proportion of letter, digit, and other characters across sampled values."""
92
+
93
+ model_config = ConfigDict(extra="allow")
94
+
95
+ letter: float
96
+ digit: float
97
+ other: float
98
+
99
+
100
+ class PatternsStatistics(BaseModel):
101
+ """The most common character patterns and the overall character composition."""
102
+
103
+ model_config = ConfigDict(extra="allow")
104
+
105
+ top: list[PatternEntry]
106
+ composition: PatternComposition
107
+
108
+
109
+ class MaskedFormEntry(BaseModel):
110
+ """A masked leading form of a value and how many sampled rows had it."""
111
+
112
+ model_config = ConfigDict(extra="allow")
113
+
114
+ masked: str
115
+ count: int
116
+
117
+
118
+ class FirstCharsStatistics(BaseModel):
119
+ """The most common masked leading forms."""
120
+
121
+ model_config = ConfigDict(extra="allow")
122
+
123
+ top: list[MaskedFormEntry]
124
+
125
+
126
+ class StringStatistics(BaseModel):
127
+ """String preview statistics; `patterns`/`first_chars` appear at or above certain disclosure levels."""
128
+
129
+ model_config = ConfigDict(extra="allow")
130
+
131
+ lengths: LengthsStatistics
132
+ patterns: Optional[PatternsStatistics] = None
133
+ first_chars: Optional[FirstCharsStatistics] = None
134
+
135
+
136
+ class NumericSummaries(BaseModel):
137
+ """Numeric mean and percentile summary."""
138
+
139
+ model_config = ConfigDict(extra="allow")
140
+
141
+ mean: float
142
+ q1: float
143
+ q2: float
144
+ q3: float
145
+ p5: float
146
+ p95: float
147
+
148
+
149
+ class NumericBin(BaseModel):
150
+ """One half-open interval `[lower_bound, upper_bound)` of a numeric histogram and its row count."""
151
+
152
+ model_config = ConfigDict(extra="allow")
153
+
154
+ lower_bound: float
155
+ upper_bound: float
156
+ count: int
157
+
158
+
159
+ class NumericHistograms(BaseModel):
160
+ """The bins of a numeric histogram."""
161
+
162
+ model_config = ConfigDict(extra="allow")
163
+
164
+ bins: list[NumericBin]
165
+
166
+
167
+ class NumericStatistics(BaseModel):
168
+ """Numeric preview statistics; `histograms` is present only at the histogram disclosure level."""
169
+
170
+ model_config = ConfigDict(extra="allow")
171
+
172
+ summaries: NumericSummaries
173
+ histograms: Optional[NumericHistograms] = None
174
+
175
+
176
+ class TemporalSummaries(BaseModel):
177
+ """Temporal mean and percentile summary, each an ISO-formatted string."""
178
+
179
+ model_config = ConfigDict(extra="allow")
180
+
181
+ mean: str
182
+ q1: str
183
+ q2: str
184
+ q3: str
185
+ p5: str
186
+ p95: str
187
+
188
+
189
+ class TemporalBin(BaseModel):
190
+ """One interval of a temporal histogram, with ISO-formatted bounds and a human-readable label."""
191
+
192
+ model_config = ConfigDict(extra="allow")
193
+
194
+ lower_bound: str
195
+ upper_bound: str
196
+ label: str
197
+ count: int
198
+
199
+
200
+ class TemporalHistograms(BaseModel):
201
+ """The bins of a temporal histogram."""
202
+
203
+ model_config = ConfigDict(extra="allow")
204
+
205
+ bins: list[TemporalBin]
206
+
207
+
208
+ class TemporalStatistics(BaseModel):
209
+ """Temporal preview statistics; `histograms` is present only at the histogram disclosure level."""
210
+
211
+ model_config = ConfigDict(extra="allow")
212
+
213
+ summaries: TemporalSummaries
214
+ histograms: Optional[TemporalHistograms] = None
215
+
216
+
217
+ class BooleanStatistics(BaseModel):
218
+ """Counts of true and false values."""
219
+
220
+ model_config = ConfigDict(extra="allow")
221
+
222
+ count_true: int
223
+ count_false: int
224
+
225
+
226
+ class UnsupportedStatistics(BaseModel):
227
+ """Why a column could not be previewed."""
228
+
229
+ model_config = ConfigDict(extra="allow")
230
+
231
+ reason: str
232
+
233
+
234
+ class ColumnPreview(BaseModel):
235
+ """Fields present on every Safe Data Preview."""
236
+
237
+ model_config = ConfigDict(extra="allow")
238
+
239
+ # For grouped file discovery, the file whose sample produced this preview; None for database columns.
240
+ sampled_from: Optional[str] = None
241
+ statistics_common: CommonStatistics
242
+
243
+
244
+ class StringPreview(ColumnPreview):
245
+ """A preview of a string column."""
246
+
247
+ kind: Literal[ColumnKind.string] = ColumnKind.string
248
+ statistics_kind: StringStatistics
249
+
250
+
251
+ class NumericPreview(ColumnPreview):
252
+ """A preview of a numeric column."""
253
+
254
+ kind: Literal[ColumnKind.numeric] = ColumnKind.numeric
255
+ statistics_kind: NumericStatistics
256
+
257
+
258
+ class TemporalPreview(ColumnPreview):
259
+ """A preview of a date/time column."""
260
+
261
+ kind: Literal[ColumnKind.temporal] = ColumnKind.temporal
262
+ statistics_kind: TemporalStatistics
263
+
264
+
265
+ class BooleanPreview(ColumnPreview):
266
+ """A preview of a boolean column."""
267
+
268
+ kind: Literal[ColumnKind.boolean] = ColumnKind.boolean
269
+ statistics_kind: BooleanStatistics
270
+
271
+
272
+ class UnsupportedPreview(ColumnPreview):
273
+ """A column the preview ran against but declined; `statistics_kind.reason` says why."""
274
+
275
+ kind: Literal[ColumnKind.unsupported] = ColumnKind.unsupported
276
+ statistics_kind: UnsupportedStatistics
277
+
278
+
279
+ SafeDataPreview = Annotated[
280
+ Union[StringPreview, NumericPreview, TemporalPreview, BooleanPreview, UnsupportedPreview],
281
+ Field(discriminator="kind"),
282
+ ]