datamasque-python 1.3.1__tar.gz → 1.5.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.
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/CONTRIBUTING.rst +6 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/HISTORY.rst +57 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/PKG-INFO +1 -1
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/__init__.py +10 -2
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/connection.py +224 -23
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/ruleset.py +7 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/ruleset_library.py +7 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/ruleset_libraries.py +4 -1
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/rulesets.py +2 -1
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/pyproject.toml +1 -1
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/helpers.py +2 -35
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_connections.py +488 -247
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_ruleset_library.py +35 -1
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_rulesets.py +84 -1
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/uv.lock +1 -1
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/.editorconfig +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/.github/workflows/ci.yml +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/.github/workflows/release-testpypi.yml +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/.github/workflows/release.yml +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/.gitignore +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/.readthedocs.yaml +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/LICENSE +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/MANIFEST.in +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/Makefile +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/NOTICE +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/README.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/RELEASING.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/base.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/connections.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/discovery.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/discovery_config_libraries.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/discovery_configs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/dmclient.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/exceptions.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/files.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/ifm.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/license.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/__init__.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/data_selection.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/discovery.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/discovery_config.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/discovery_config_library.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/dm_instance.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/files.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/git.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/ifm.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/license.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/pagination.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/rg_config.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/runs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/safe_data_preview.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/status.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/table_reference.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/user.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/py.typed +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/rg_configs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/runs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/settings.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/spcs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/table_references.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/users.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/Makefile +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/client.models.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/client.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/conf.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/contributing.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/history.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/index.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/installation.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/make.bat +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/modules.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/readme.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/docs/usage.rst +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/__init__.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/conftest.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_base.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_discovery.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_discovery_config_libraries.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_discovery_configs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_files.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_ifm.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_license.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_pagination.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_rg_configs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_runs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_safe_data_preview.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_settings.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_spcs.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_table_references.py +0 -0
- {datamasque_python-1.3.1 → datamasque_python-1.5.0}/tests/test_users.py +0 -0
|
@@ -149,6 +149,12 @@ Pull requests
|
|
|
149
149
|
5. Open a PR against ``main`` and describe what the change does and why.
|
|
150
150
|
6. The maintainers will review and either merge, request changes, or close with an explanation.
|
|
151
151
|
|
|
152
|
+
Changelog
|
|
153
|
+
=========
|
|
154
|
+
|
|
155
|
+
Create a new version heading in ``HISTORY.rst``,
|
|
156
|
+
and add one concise bullet point for each high-level change.
|
|
157
|
+
|
|
152
158
|
Commit messages
|
|
153
159
|
===============
|
|
154
160
|
|
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
History
|
|
3
3
|
=======
|
|
4
4
|
|
|
5
|
+
1.5.0 (2026-10-07)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
* **Breaking:** Snowflake masking now stages inside Snowflake, so ``SnowflakeConnectionConfig``
|
|
9
|
+
no longer has the external staging fields ``s3_bucket_name``, ``iam_role_arn``,
|
|
10
|
+
``snowflake_azure_container_name``, ``snowflake_azure_connection_string``,
|
|
11
|
+
``snowflake_azure_connection_string_encrypted`` and ``snowflake_storage_integration_name``.
|
|
12
|
+
``snowflake_stage_location`` and the ``SnowflakeStageLocation`` enum are removed: the server
|
|
13
|
+
detects a Snowflake SPCS deployment from its own environment, so the client no longer states it.
|
|
14
|
+
* Snowflake connections listed from an older server still parse: the removed fields and
|
|
15
|
+
``snowflake_stage_location`` are dropped rather than raising a validation error, and are not sent
|
|
16
|
+
back on ``create_or_update_connection``.
|
|
17
|
+
* Added ``GcsConnectionConfig`` for Google Cloud Storage file connections (``type: gcs_connection``),
|
|
18
|
+
with ``bucket`` and ``service_account_key``:
|
|
19
|
+
the contents of a service account's JSON key file, or the ARN of an AWS Secrets Manager secret that holds it.
|
|
20
|
+
``validate_connection`` and ``list_connections`` accept the new type.
|
|
21
|
+
* Added ``CassandraConnectionConfig`` for Apache Cassandra (``db_type: cassandra``).
|
|
22
|
+
``database`` is the keyspace; ``user`` and ``password`` are optional,
|
|
23
|
+
and ``local_datacenter``, ``tls`` and ``direct_connection`` are supported.
|
|
24
|
+
* Added ``SapAseConnectionConfig`` for SAP ASE (Sybase) (``db_type: sap_ase``),
|
|
25
|
+
with ``tls``, ``tls_server_name`` and ``connect_timeout``.
|
|
26
|
+
It sends no ``schema``: in ASE the schema is the table owner.
|
|
27
|
+
* Added ``SalesforceConnectionConfig`` for Salesforce (``db_type: salesforce``),
|
|
28
|
+
which signs in with an OAuth JWT bearer assertion
|
|
29
|
+
(``instance_url``, ``login_url``, ``client_id``, ``user`` and ``salesforce_private_key_path``,
|
|
30
|
+
with an optional ``salesforce_private_key_passphrase`` and ``api_version``).
|
|
31
|
+
Salesforce is a preview connection type on server 3.26.19:
|
|
32
|
+
creating one fails until an administrator enables preview features.
|
|
33
|
+
* ``DatabaseType`` gains ``cassandra``, ``sap_ase`` and ``salesforce``.
|
|
34
|
+
``DatabaseConnectionConfig`` rejects them in favour of the classes above,
|
|
35
|
+
and ``validate_connection`` and ``list_connections`` dispatch to those classes.
|
|
36
|
+
|
|
37
|
+
Requires server version 3.26.19
|
|
38
|
+
|
|
39
|
+
1.4.0 (2026-09-25)
|
|
40
|
+
------------------
|
|
41
|
+
|
|
42
|
+
* Added ``LicenseLock`` (``locked_at`` and ``editable_from``) and a read-only ``license_lock`` field on connection configs.
|
|
43
|
+
The server reports it only on instances whose license caps the number of connections
|
|
44
|
+
and counts that connection's type against the cap,
|
|
45
|
+
once a connection's first run has finished successfully;
|
|
46
|
+
otherwise it is ``None``.
|
|
47
|
+
It is excluded when a config is serialized back to the API,
|
|
48
|
+
as it must never be sent on create or update.
|
|
49
|
+
|
|
50
|
+
* Added ``unlicensed_feature_warnings`` to ``Ruleset`` and ``RulesetLibrary``: at most one sentence naming the features
|
|
51
|
+
each uses that the server's license does not allow.
|
|
52
|
+
|
|
53
|
+
* Added read-only ``target_frozen``, ``frozen_target_fields`` and ``deletion_frozen`` fields on connection configs.
|
|
54
|
+
``target_frozen`` is ``True`` while the connection's protected fields are locked under the license edit lock,
|
|
55
|
+
``frozen_target_fields`` lists those locked fields as server config keys, such as ``schema`` (empty when not frozen),
|
|
56
|
+
and ``deletion_frozen`` is whether deleting the connection is currently blocked
|
|
57
|
+
(it can differ from ``target_frozen``: deletion stays allowed while over the connection cap).
|
|
58
|
+
All three are excluded when a config is serialized back to the API.
|
|
59
|
+
|
|
60
|
+
Requires server version 3.26.18
|
|
61
|
+
|
|
5
62
|
1.3.1 (2026-09-09)
|
|
6
63
|
------------------
|
|
7
64
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: datamasque-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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/
|
|
@@ -29,6 +29,7 @@ from datamasque.client.exceptions import (
|
|
|
29
29
|
from datamasque.client.ifm import DataMasqueIfmClient
|
|
30
30
|
from datamasque.client.models.connection import (
|
|
31
31
|
AzureConnectionConfig,
|
|
32
|
+
CassandraConnectionConfig,
|
|
32
33
|
ConnectionConfig,
|
|
33
34
|
ConnectionId,
|
|
34
35
|
CosmosDbConnectionConfig,
|
|
@@ -38,12 +39,15 @@ from datamasque.client.models.connection import (
|
|
|
38
39
|
DocumentDbConnectionConfig,
|
|
39
40
|
DynamoConnectionConfig,
|
|
40
41
|
FileConnectionConfig,
|
|
42
|
+
GcsConnectionConfig,
|
|
43
|
+
LicenseLock,
|
|
41
44
|
MongoConnectionConfig,
|
|
42
45
|
MountedShareConnectionConfig,
|
|
43
46
|
MssqlLinkedServerConnectionConfig,
|
|
44
47
|
S3ConnectionConfig,
|
|
48
|
+
SalesforceConnectionConfig,
|
|
49
|
+
SapAseConnectionConfig,
|
|
45
50
|
SnowflakeConnectionConfig,
|
|
46
|
-
SnowflakeStageLocation,
|
|
47
51
|
SseConfig,
|
|
48
52
|
SseSelection,
|
|
49
53
|
)
|
|
@@ -178,6 +182,7 @@ __all__ = [
|
|
|
178
182
|
"AzureConnectionConfig",
|
|
179
183
|
"BooleanPreview",
|
|
180
184
|
"BooleanStatistics",
|
|
185
|
+
"CassandraConnectionConfig",
|
|
181
186
|
"ColumnKind",
|
|
182
187
|
"ColumnPreview",
|
|
183
188
|
"CommonStatistics",
|
|
@@ -225,6 +230,7 @@ __all__ = [
|
|
|
225
230
|
"FileRulesetGenerationWithRGConfigRequest",
|
|
226
231
|
"FirstCharsStatistics",
|
|
227
232
|
"ForeignKeyRef",
|
|
233
|
+
"GcsConnectionConfig",
|
|
228
234
|
"GitSnapshot",
|
|
229
235
|
"HashColumnsTableConfig",
|
|
230
236
|
"IfmAuthError",
|
|
@@ -243,6 +249,7 @@ __all__ = [
|
|
|
243
249
|
"LengthUnit",
|
|
244
250
|
"LengthsStatistics",
|
|
245
251
|
"LicenseInfo",
|
|
252
|
+
"LicenseLock",
|
|
246
253
|
"Locator",
|
|
247
254
|
"MaskType",
|
|
248
255
|
"MaskedFormEntry",
|
|
@@ -285,6 +292,8 @@ __all__ = [
|
|
|
285
292
|
"S3ConnectionConfig",
|
|
286
293
|
"SafeDataPreview",
|
|
287
294
|
"SafeDataPreviewOptions",
|
|
295
|
+
"SalesforceConnectionConfig",
|
|
296
|
+
"SapAseConnectionConfig",
|
|
288
297
|
"SchemaDiscoveryColumn",
|
|
289
298
|
"SchemaDiscoveryFromConfigRequest",
|
|
290
299
|
"SchemaDiscoveryPage",
|
|
@@ -296,7 +305,6 @@ __all__ = [
|
|
|
296
305
|
"SelectedFileData",
|
|
297
306
|
"SnowflakeConnectionConfig",
|
|
298
307
|
"SnowflakeKeyFile",
|
|
299
|
-
"SnowflakeStageLocation",
|
|
300
308
|
"SseConfig",
|
|
301
309
|
"SseSelection",
|
|
302
310
|
"SslZipFile",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Connection configuration models for the DataMasque API."""
|
|
2
2
|
|
|
3
|
+
from datetime import datetime
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any, Callable, Literal, NewType, Optional
|
|
5
6
|
|
|
@@ -50,15 +51,9 @@ class DatabaseType(Enum):
|
|
|
50
51
|
databricks = "databricks"
|
|
51
52
|
informix = "informix"
|
|
52
53
|
saphana = "saphana"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"""Storage backend for a Snowflake connection's external stage."""
|
|
57
|
-
|
|
58
|
-
local = "local" # Not supported for production use
|
|
59
|
-
aws_s3 = "aws_s3"
|
|
60
|
-
azure_blob_storage = "azure_blob_storage"
|
|
61
|
-
spcs = "spcs" # DataMasque running inside Snowflake SPCS; staged on the container's own storage
|
|
54
|
+
cassandra = "cassandra"
|
|
55
|
+
sap_ase = "sap_ase"
|
|
56
|
+
salesforce = "salesforce"
|
|
62
57
|
|
|
63
58
|
|
|
64
59
|
class SseSelection(Enum):
|
|
@@ -99,6 +94,22 @@ class SseConfig(BaseModel):
|
|
|
99
94
|
return self
|
|
100
95
|
|
|
101
96
|
|
|
97
|
+
class LicenseLock(BaseModel):
|
|
98
|
+
"""
|
|
99
|
+
Read-only license-lock metadata for a connection.
|
|
100
|
+
|
|
101
|
+
The server reports this only on instances whose license has a finite connection cap
|
|
102
|
+
and counts this connection's type against that cap,
|
|
103
|
+
once the connection's first run has finished successfully;
|
|
104
|
+
otherwise it is `null`.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
model_config = ConfigDict(extra="allow")
|
|
108
|
+
|
|
109
|
+
locked_at: datetime
|
|
110
|
+
editable_from: datetime
|
|
111
|
+
|
|
112
|
+
|
|
102
113
|
class ConnectionConfig(BaseModel):
|
|
103
114
|
"""
|
|
104
115
|
Base class for all connection configurations.
|
|
@@ -111,6 +122,16 @@ class ConnectionConfig(BaseModel):
|
|
|
111
122
|
|
|
112
123
|
name: str
|
|
113
124
|
id: Optional[ConnectionId] = None
|
|
125
|
+
# Server-populated and read-only, so it is excluded when serializing a config back to the API.
|
|
126
|
+
license_lock: Optional[LicenseLock] = Field(default=None, exclude=True)
|
|
127
|
+
# Whether the connection's protected fields are currently locked under the license edit lock.
|
|
128
|
+
target_frozen: bool = Field(default=False, exclude=True)
|
|
129
|
+
# The protected fields currently locked, as server config keys (e.g. `schema`, not `db_schema`);
|
|
130
|
+
# empty when `target_frozen` is False.
|
|
131
|
+
frozen_target_fields: list[str] = Field(default_factory=list, exclude=True)
|
|
132
|
+
# Whether deleting the connection is currently blocked. Not always equal to `target_frozen`:
|
|
133
|
+
# deletion stays allowed while over the connection cap, so a downgrade can be recovered from.
|
|
134
|
+
deletion_frozen: bool = Field(default=False, exclude=True)
|
|
114
135
|
|
|
115
136
|
|
|
116
137
|
class DynamoConnectionConfig(ConnectionConfig):
|
|
@@ -258,6 +279,19 @@ class CosmosDbConnectionConfig(MongoConnectionConfig):
|
|
|
258
279
|
return DatabaseType.cosmosdb
|
|
259
280
|
|
|
260
281
|
|
|
282
|
+
# Servers before 3.26.18 still return these on Snowflake connections. With `extra="allow"`
|
|
283
|
+
# they would be sent back on create/update unless dropped here.
|
|
284
|
+
_SNOWFLAKE_EXTERNAL_STAGE_KEYS = (
|
|
285
|
+
"s3_bucket_name",
|
|
286
|
+
"iam_role_arn",
|
|
287
|
+
"snowflake_azure_container_name",
|
|
288
|
+
"snowflake_azure_connection_string",
|
|
289
|
+
"snowflake_azure_connection_string_encrypted",
|
|
290
|
+
"snowflake_storage_integration_name",
|
|
291
|
+
"snowflake_stage_location",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
261
295
|
class SnowflakeConnectionConfig(ConnectionConfig):
|
|
262
296
|
"""
|
|
263
297
|
Connection configuration for a Snowflake database.
|
|
@@ -267,14 +301,14 @@ class SnowflakeConnectionConfig(ConnectionConfig):
|
|
|
267
301
|
"""
|
|
268
302
|
|
|
269
303
|
database: str
|
|
270
|
-
# Optional because
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
#
|
|
304
|
+
# Optional because connections leave these unset when DataMasque runs inside Snowflake SPCS:
|
|
305
|
+
# the server detects SPCS from its environment and the agent uses the container's OAuth token,
|
|
306
|
+
# the SNOWFLAKE_HOST/SNOWFLAKE_ACCOUNT env and the app-owned QUERY_WAREHOUSE, so user/account/
|
|
307
|
+
# warehouse are None. Mirrors the app's canonical model
|
|
308
|
+
# (agent .../schemas/connection/connection.py), which types these `| None = None`.
|
|
274
309
|
user: Optional[str] = None
|
|
275
310
|
snowflake_account_id: Optional[str] = None
|
|
276
311
|
snowflake_warehouse: Optional[str] = None
|
|
277
|
-
snowflake_storage_integration_name: Optional[str] = None
|
|
278
312
|
host: str = ""
|
|
279
313
|
port: Optional[int] = None
|
|
280
314
|
db_schema: Optional[str] = Field(default=None, alias="schema")
|
|
@@ -283,12 +317,6 @@ class SnowflakeConnectionConfig(ConnectionConfig):
|
|
|
283
317
|
password: Optional[str] = None
|
|
284
318
|
snowflake_private_key: Optional[FileId] = None
|
|
285
319
|
snowflake_private_key_passphrase: Optional[str] = None
|
|
286
|
-
snowflake_stage_location: Optional[SnowflakeStageLocation] = None
|
|
287
|
-
s3_bucket_name: Optional[str] = None
|
|
288
|
-
iam_role_arn: Optional[str] = None
|
|
289
|
-
snowflake_azure_container_name: Optional[str] = None
|
|
290
|
-
snowflake_azure_connection_string: Optional[str] = None
|
|
291
|
-
snowflake_azure_connection_string_encrypted: Optional[str] = None
|
|
292
320
|
|
|
293
321
|
mask_type: Literal["database"] = "database"
|
|
294
322
|
db_type: Literal["snowflake"] = "snowflake"
|
|
@@ -311,9 +339,9 @@ class SnowflakeConnectionConfig(ConnectionConfig):
|
|
|
311
339
|
|
|
312
340
|
@model_validator(mode="before")
|
|
313
341
|
@classmethod
|
|
314
|
-
def
|
|
342
|
+
def _strip_server_only_fields(cls, data: dict) -> dict:
|
|
315
343
|
if isinstance(data, dict):
|
|
316
|
-
for key in ("password_encrypted", "dbpassword"):
|
|
344
|
+
for key in ("password_encrypted", "dbpassword", *_SNOWFLAKE_EXTERNAL_STAGE_KEYS):
|
|
317
345
|
data.pop(key, None)
|
|
318
346
|
return data
|
|
319
347
|
|
|
@@ -339,7 +367,8 @@ class DatabaseConnectionConfig(ConnectionConfig):
|
|
|
339
367
|
Connection configuration for a SQL database.
|
|
340
368
|
|
|
341
369
|
Use `DynamoConnectionConfig` for DynamoDB, `SnowflakeConnectionConfig` for Snowflake,
|
|
342
|
-
|
|
370
|
+
`MongoConnectionConfig` for MongoDB, `CassandraConnectionConfig` for Apache Cassandra,
|
|
371
|
+
`SapAseConnectionConfig` for SAP ASE (Sybase) and `SalesforceConnectionConfig` for Salesforce.
|
|
343
372
|
"""
|
|
344
373
|
|
|
345
374
|
host: str
|
|
@@ -374,6 +403,12 @@ class DatabaseConnectionConfig(ConnectionConfig):
|
|
|
374
403
|
raise ValueError("For Azure Cosmos DB, use the CosmosDbConnectionConfig class instead")
|
|
375
404
|
if self.database_type is DatabaseType.databricks:
|
|
376
405
|
raise ValueError("For Databricks SQL Warehouse, use the DatabricksConnectionConfig class instead")
|
|
406
|
+
if self.database_type is DatabaseType.cassandra:
|
|
407
|
+
raise ValueError("For Apache Cassandra, use the CassandraConnectionConfig class instead")
|
|
408
|
+
if self.database_type is DatabaseType.sap_ase:
|
|
409
|
+
raise ValueError("For SAP ASE (Sybase), use the SapAseConnectionConfig class instead")
|
|
410
|
+
if self.database_type is DatabaseType.salesforce:
|
|
411
|
+
raise ValueError("For Salesforce, use the SalesforceConnectionConfig class instead")
|
|
377
412
|
return self
|
|
378
413
|
|
|
379
414
|
mask_type: Literal["database"] = "database"
|
|
@@ -481,6 +516,29 @@ class AzureConnectionConfig(FileConnectionConfig):
|
|
|
481
516
|
return data
|
|
482
517
|
|
|
483
518
|
|
|
519
|
+
class GcsConnectionConfig(FileConnectionConfig):
|
|
520
|
+
"""
|
|
521
|
+
Connection configuration for a Google Cloud Storage bucket.
|
|
522
|
+
|
|
523
|
+
`service_account_key` holds the contents of a service account's JSON key file,
|
|
524
|
+
or the ARN of an AWS Secrets Manager secret that holds it.
|
|
525
|
+
|
|
526
|
+
Requires server version 3.26.19.
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
type: Literal["gcs_connection"] = "gcs_connection"
|
|
530
|
+
bucket: str = ""
|
|
531
|
+
service_account_key: Optional[str] = None
|
|
532
|
+
|
|
533
|
+
@model_validator(mode="before")
|
|
534
|
+
@classmethod
|
|
535
|
+
def _strip_encrypted_service_account_key(cls, data: dict) -> dict:
|
|
536
|
+
if isinstance(data, dict):
|
|
537
|
+
# The API returns the encrypted form; drop it so `service_account_key` stays None.
|
|
538
|
+
data.pop("service_account_key_encrypted", None)
|
|
539
|
+
return data
|
|
540
|
+
|
|
541
|
+
|
|
484
542
|
class MountedShareConnectionConfig(FileConnectionConfig):
|
|
485
543
|
"""Connection configuration for a mounted file share."""
|
|
486
544
|
|
|
@@ -513,9 +571,149 @@ class DatabricksConnectionConfig(ConnectionConfig):
|
|
|
513
571
|
return data
|
|
514
572
|
|
|
515
573
|
|
|
574
|
+
class CassandraConnectionConfig(ConnectionConfig):
|
|
575
|
+
"""
|
|
576
|
+
Connection configuration for an Apache Cassandra keyspace.
|
|
577
|
+
|
|
578
|
+
`database` is the keyspace.
|
|
579
|
+
`user` and `password` are optional, but a username needs a password.
|
|
580
|
+
Set `direct_connection` when the node addresses the cluster advertises are unreachable,
|
|
581
|
+
for example behind NAT or in containers.
|
|
582
|
+
|
|
583
|
+
Requires server version 3.26.19.
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
host: str
|
|
587
|
+
port: int = 9042
|
|
588
|
+
database: str
|
|
589
|
+
user: Optional[str] = None
|
|
590
|
+
password: Optional[str] = None
|
|
591
|
+
local_datacenter: Optional[str] = None
|
|
592
|
+
tls: bool = False
|
|
593
|
+
direct_connection: bool = False
|
|
594
|
+
is_read_only: bool = False
|
|
595
|
+
|
|
596
|
+
mask_type: Literal["database"] = "database"
|
|
597
|
+
db_type: Literal["cassandra"] = "cassandra"
|
|
598
|
+
|
|
599
|
+
@property
|
|
600
|
+
def database_type(self) -> DatabaseType:
|
|
601
|
+
return DatabaseType.cassandra
|
|
602
|
+
|
|
603
|
+
@model_serializer(mode="wrap")
|
|
604
|
+
def _serialize(self, handler: Callable) -> dict:
|
|
605
|
+
d = handler(self)
|
|
606
|
+
# The server expects the password under the `dbpassword` key.
|
|
607
|
+
password = d.pop("password", None)
|
|
608
|
+
if password is not None:
|
|
609
|
+
d["dbpassword"] = password
|
|
610
|
+
return d
|
|
611
|
+
|
|
612
|
+
@model_validator(mode="before")
|
|
613
|
+
@classmethod
|
|
614
|
+
def _strip_encrypted_password(cls, data: dict) -> dict:
|
|
615
|
+
if isinstance(data, dict):
|
|
616
|
+
for key in ("password_encrypted", "dbpassword"):
|
|
617
|
+
data.pop(key, None)
|
|
618
|
+
return data
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
class SapAseConnectionConfig(ConnectionConfig):
|
|
622
|
+
"""
|
|
623
|
+
Connection configuration for an SAP ASE (Sybase) database.
|
|
624
|
+
|
|
625
|
+
ASE has no schema setting: the schema is the table owner,
|
|
626
|
+
and an unqualified name resolves to the connecting user's table first, then to `dbo`'s.
|
|
627
|
+
With `tls` on, the server certificate is always verified;
|
|
628
|
+
`tls_server_name` only changes the name it is verified against.
|
|
629
|
+
|
|
630
|
+
Requires server version 3.26.19.
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
host: str
|
|
634
|
+
port: int = 5000
|
|
635
|
+
database: str
|
|
636
|
+
user: str
|
|
637
|
+
password: Optional[str] = None
|
|
638
|
+
tls: bool = False
|
|
639
|
+
tls_server_name: Optional[str] = None
|
|
640
|
+
connect_timeout: Optional[int] = None
|
|
641
|
+
is_read_only: bool = False
|
|
642
|
+
|
|
643
|
+
mask_type: Literal["database"] = "database"
|
|
644
|
+
db_type: Literal["sap_ase"] = "sap_ase"
|
|
645
|
+
|
|
646
|
+
@property
|
|
647
|
+
def database_type(self) -> DatabaseType:
|
|
648
|
+
return DatabaseType.sap_ase
|
|
649
|
+
|
|
650
|
+
@model_serializer(mode="wrap")
|
|
651
|
+
def _serialize(self, handler: Callable) -> dict:
|
|
652
|
+
d = handler(self)
|
|
653
|
+
# The server expects the password under the `dbpassword` key.
|
|
654
|
+
password = d.pop("password", None)
|
|
655
|
+
if password is not None:
|
|
656
|
+
d["dbpassword"] = password
|
|
657
|
+
return d
|
|
658
|
+
|
|
659
|
+
@model_validator(mode="before")
|
|
660
|
+
@classmethod
|
|
661
|
+
def _strip_encrypted_password(cls, data: dict) -> dict:
|
|
662
|
+
if isinstance(data, dict):
|
|
663
|
+
for key in ("password_encrypted", "dbpassword"):
|
|
664
|
+
data.pop(key, None)
|
|
665
|
+
return data
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class SalesforceConnectionConfig(ConnectionConfig):
|
|
669
|
+
"""
|
|
670
|
+
Connection configuration for a Salesforce org.
|
|
671
|
+
|
|
672
|
+
Salesforce is reached over HTTPS with an OAuth JWT bearer assertion,
|
|
673
|
+
so it carries no host, port, database or password.
|
|
674
|
+
`login_url` is the org's My Domain URL and is sent verbatim as the JWT audience,
|
|
675
|
+
which Salesforce compares exactly.
|
|
676
|
+
`salesforce_private_key_path` is relative to the DataMasque files sandbox,
|
|
677
|
+
or to the connection fileset when one is attached.
|
|
678
|
+
`salesforce_private_key_passphrase` is only needed for an encrypted key.
|
|
679
|
+
`api_version` is the server's default unless set.
|
|
680
|
+
|
|
681
|
+
Requires server version 3.26.19.
|
|
682
|
+
Salesforce is a preview connection type there:
|
|
683
|
+
the server refuses to create one until an administrator enables preview features
|
|
684
|
+
under Settings, Preview Features.
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
instance_url: str
|
|
688
|
+
login_url: str
|
|
689
|
+
client_id: str
|
|
690
|
+
user: str
|
|
691
|
+
salesforce_private_key_path: str
|
|
692
|
+
salesforce_private_key_passphrase: Optional[str] = None
|
|
693
|
+
# Left unset so the server applies its own default, which it stores on the connection.
|
|
694
|
+
# A default here would keep pinning new connections to it after the server moves on.
|
|
695
|
+
api_version: Optional[str] = None
|
|
696
|
+
is_read_only: bool = False
|
|
697
|
+
|
|
698
|
+
mask_type: Literal["database"] = "database"
|
|
699
|
+
db_type: Literal["salesforce"] = "salesforce"
|
|
700
|
+
|
|
701
|
+
@property
|
|
702
|
+
def database_type(self) -> DatabaseType:
|
|
703
|
+
return DatabaseType.salesforce
|
|
704
|
+
|
|
705
|
+
@model_validator(mode="before")
|
|
706
|
+
@classmethod
|
|
707
|
+
def _strip_encrypted_passphrase(cls, data: dict) -> dict:
|
|
708
|
+
if isinstance(data, dict):
|
|
709
|
+
data.pop("salesforce_private_key_passphrase_encrypted", None)
|
|
710
|
+
return data
|
|
711
|
+
|
|
712
|
+
|
|
516
713
|
FILE_TYPE_MAP: dict[str, type[FileConnectionConfig]] = {
|
|
517
714
|
"s3_connection": S3ConnectionConfig,
|
|
518
715
|
"azure_blob_connection": AzureConnectionConfig,
|
|
716
|
+
"gcs_connection": GcsConnectionConfig,
|
|
519
717
|
"mounted_share_connection": MountedShareConnectionConfig,
|
|
520
718
|
}
|
|
521
719
|
|
|
@@ -527,6 +725,9 @@ DB_TYPE_MAP: dict[str, type[ConnectionConfig]] = {
|
|
|
527
725
|
DatabaseType.snowflake.value: SnowflakeConnectionConfig,
|
|
528
726
|
DatabaseType.mssql_linked.value: MssqlLinkedServerConnectionConfig,
|
|
529
727
|
DatabaseType.databricks.value: DatabricksConnectionConfig,
|
|
728
|
+
DatabaseType.cassandra.value: CassandraConnectionConfig,
|
|
729
|
+
DatabaseType.sap_ase.value: SapAseConnectionConfig,
|
|
730
|
+
DatabaseType.salesforce.value: SalesforceConnectionConfig,
|
|
530
731
|
# others use the default `DatabaseConnectionConfig`
|
|
531
732
|
}
|
|
532
733
|
|
|
@@ -46,3 +46,10 @@ class Ruleset(GitTrackedEntity):
|
|
|
46
46
|
is_valid: Optional[ValidationStatus] = Field(default=None, exclude=True)
|
|
47
47
|
validation_errors: list[ValidationErrorDetails] = Field(default_factory=list, exclude=True)
|
|
48
48
|
"""Validation errors surfaced by the server; empty when valid."""
|
|
49
|
+
unlicensed_feature_warnings: Optional[list[str]] = Field(default=None, exclude=True)
|
|
50
|
+
"""
|
|
51
|
+
At most one sentence, naming every feature the ruleset uses that the server's license does not allow.
|
|
52
|
+
Empty when the ruleset needs nothing its license withholds,
|
|
53
|
+
and `None` when the server does not know which features the ruleset uses, for example before it has passed
|
|
54
|
+
validation. `None` is not the same as having no warnings.
|
|
55
|
+
"""
|
{datamasque_python-1.3.1 → datamasque_python-1.5.0}/datamasque/client/models/ruleset_library.py
RENAMED
|
@@ -21,5 +21,12 @@ class RulesetLibrary(GitTrackedEntity):
|
|
|
21
21
|
is_valid: Optional[ValidationStatus] = Field(default=None, exclude=True)
|
|
22
22
|
validation_errors: list[ValidationErrorDetails] = Field(default_factory=list, exclude=True)
|
|
23
23
|
"""Validation errors surfaced by the server; empty when valid."""
|
|
24
|
+
unlicensed_feature_warnings: Optional[list[str]] = Field(default=None, exclude=True)
|
|
25
|
+
"""
|
|
26
|
+
At most one sentence, naming every feature the library uses that the server's license does not allow.
|
|
27
|
+
Empty when the license allows every feature,
|
|
28
|
+
and `None` when the server does not know which features the library uses, for example before it has passed
|
|
29
|
+
validation.
|
|
30
|
+
"""
|
|
24
31
|
created: Optional[datetime] = Field(default=None, exclude=True)
|
|
25
32
|
modified: Optional[datetime] = Field(default=None, exclude=True)
|
|
@@ -64,7 +64,8 @@ class RulesetLibraryClient(BaseClient):
|
|
|
64
64
|
Creates a new ruleset library on the server.
|
|
65
65
|
|
|
66
66
|
Sets the library's server-assigned fields
|
|
67
|
-
(`id`, `is_valid`, `validation_errors`, `git`, `created`, `modified`)
|
|
67
|
+
(`id`, `is_valid`, `validation_errors`, `unlicensed_feature_warnings`, `git`, `created`, `modified`)
|
|
68
|
+
and returns the library.
|
|
68
69
|
"""
|
|
69
70
|
|
|
70
71
|
data = library.model_dump(exclude_none=True, by_alias=True, mode="json")
|
|
@@ -73,6 +74,7 @@ class RulesetLibraryClient(BaseClient):
|
|
|
73
74
|
library.id = created_library.id
|
|
74
75
|
library.is_valid = created_library.is_valid
|
|
75
76
|
library.validation_errors = created_library.validation_errors
|
|
77
|
+
library.unlicensed_feature_warnings = created_library.unlicensed_feature_warnings
|
|
76
78
|
library.git = created_library.git
|
|
77
79
|
library.created = created_library.created
|
|
78
80
|
library.modified = created_library.modified
|
|
@@ -94,6 +96,7 @@ class RulesetLibraryClient(BaseClient):
|
|
|
94
96
|
updated_library = RulesetLibrary.model_validate(response.json())
|
|
95
97
|
library.is_valid = updated_library.is_valid
|
|
96
98
|
library.validation_errors = updated_library.validation_errors
|
|
99
|
+
library.unlicensed_feature_warnings = updated_library.unlicensed_feature_warnings
|
|
97
100
|
library.git = updated_library.git
|
|
98
101
|
library.modified = updated_library.modified
|
|
99
102
|
logger.debug('Update of ruleset library "%s" successful', library.name)
|
|
@@ -20,7 +20,7 @@ class RulesetClient(BaseClient):
|
|
|
20
20
|
"""
|
|
21
21
|
Creates or updates a ruleset.
|
|
22
22
|
|
|
23
|
-
Populates the given ruleset's `id`, `is_valid`, `validation_errors`,
|
|
23
|
+
Populates the given ruleset's `id`, `is_valid`, `validation_errors`, `unlicensed_feature_warnings`,
|
|
24
24
|
and `git` fields from the server response, and returns the same ruleset instance for convenience.
|
|
25
25
|
"""
|
|
26
26
|
|
|
@@ -30,6 +30,7 @@ class RulesetClient(BaseClient):
|
|
|
30
30
|
ruleset.id = created.id
|
|
31
31
|
ruleset.is_valid = created.is_valid
|
|
32
32
|
ruleset.validation_errors = created.validation_errors
|
|
33
|
+
ruleset.unlicensed_feature_warnings = created.unlicensed_feature_warnings
|
|
33
34
|
ruleset.git = created.git
|
|
34
35
|
|
|
35
36
|
if response.status_code == 201:
|
|
@@ -8,7 +8,6 @@ from datamasque.client.models.connection import (
|
|
|
8
8
|
DatabaseType,
|
|
9
9
|
S3ConnectionConfig,
|
|
10
10
|
SnowflakeConnectionConfig,
|
|
11
|
-
SnowflakeStageLocation,
|
|
12
11
|
)
|
|
13
12
|
|
|
14
13
|
fake = Faker()
|
|
@@ -117,44 +116,12 @@ def make_ok_response() -> Response:
|
|
|
117
116
|
return response
|
|
118
117
|
|
|
119
118
|
|
|
120
|
-
def
|
|
119
|
+
def snowflake_connection_config():
|
|
121
120
|
return SnowflakeConnectionConfig(
|
|
122
|
-
name="
|
|
121
|
+
name="snowflake",
|
|
123
122
|
database="test_db",
|
|
124
123
|
user="snowflake_user",
|
|
125
124
|
snowflake_account_id="ACCOUNT-123",
|
|
126
125
|
snowflake_warehouse="test_warehouse",
|
|
127
|
-
snowflake_storage_integration_name="test_integration",
|
|
128
126
|
password="test_password",
|
|
129
|
-
snowflake_stage_location=SnowflakeStageLocation.aws_s3,
|
|
130
|
-
s3_bucket_name="test-bucket",
|
|
131
|
-
iam_role_arn="arn:aws:iam::123456789012:role/test-role",
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def snowflake_connection_config_azure():
|
|
136
|
-
return SnowflakeConnectionConfig(
|
|
137
|
-
name="snowflake_azure",
|
|
138
|
-
database="test_db",
|
|
139
|
-
user="snowflake_user",
|
|
140
|
-
snowflake_account_id="ACCOUNT-456",
|
|
141
|
-
snowflake_warehouse="test_warehouse",
|
|
142
|
-
snowflake_storage_integration_name="test_integration",
|
|
143
|
-
password="test_password",
|
|
144
|
-
snowflake_stage_location=SnowflakeStageLocation.azure_blob_storage,
|
|
145
|
-
snowflake_azure_container_name="test-container",
|
|
146
|
-
snowflake_azure_connection_string="DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test",
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def snowflake_connection_config_local():
|
|
151
|
-
return SnowflakeConnectionConfig(
|
|
152
|
-
name="snowflake_local",
|
|
153
|
-
database="test_db",
|
|
154
|
-
user="snowflake_user",
|
|
155
|
-
snowflake_account_id="ACCOUNT-789",
|
|
156
|
-
snowflake_warehouse="test_warehouse",
|
|
157
|
-
snowflake_storage_integration_name="test_integration",
|
|
158
|
-
password="test_password",
|
|
159
|
-
snowflake_stage_location=SnowflakeStageLocation.local,
|
|
160
127
|
)
|