datamasque-python 1.4.0__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.
Files changed (90) hide show
  1. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/HISTORY.rst +34 -0
  2. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/PKG-INFO +1 -1
  3. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/__init__.py +8 -2
  4. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/connection.py +197 -23
  5. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/pyproject.toml +1 -1
  6. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/helpers.py +2 -35
  7. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_connections.py +290 -247
  8. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/uv.lock +1 -1
  9. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/.editorconfig +0 -0
  10. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/.github/workflows/ci.yml +0 -0
  11. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/.github/workflows/release-testpypi.yml +0 -0
  12. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/.github/workflows/release.yml +0 -0
  13. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/.gitignore +0 -0
  14. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/.readthedocs.yaml +0 -0
  15. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/CONTRIBUTING.rst +0 -0
  16. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/LICENSE +0 -0
  17. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/MANIFEST.in +0 -0
  18. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/Makefile +0 -0
  19. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/NOTICE +0 -0
  20. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/README.rst +0 -0
  21. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/RELEASING.rst +0 -0
  22. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/base.py +0 -0
  23. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/connections.py +0 -0
  24. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/discovery.py +0 -0
  25. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/discovery_config_libraries.py +0 -0
  26. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/discovery_configs.py +0 -0
  27. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/dmclient.py +0 -0
  28. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/exceptions.py +0 -0
  29. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/files.py +0 -0
  30. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/ifm.py +0 -0
  31. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/license.py +0 -0
  32. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/__init__.py +0 -0
  33. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/data_selection.py +0 -0
  34. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/discovery.py +0 -0
  35. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/discovery_config.py +0 -0
  36. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/discovery_config_library.py +0 -0
  37. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/dm_instance.py +0 -0
  38. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/files.py +0 -0
  39. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/git.py +0 -0
  40. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/ifm.py +0 -0
  41. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/license.py +0 -0
  42. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/pagination.py +0 -0
  43. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/rg_config.py +0 -0
  44. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/ruleset.py +0 -0
  45. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/ruleset_library.py +0 -0
  46. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/runs.py +0 -0
  47. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/safe_data_preview.py +0 -0
  48. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/status.py +0 -0
  49. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/table_reference.py +0 -0
  50. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/models/user.py +0 -0
  51. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/py.typed +0 -0
  52. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/rg_configs.py +0 -0
  53. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/ruleset_libraries.py +0 -0
  54. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/rulesets.py +0 -0
  55. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/runs.py +0 -0
  56. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/settings.py +0 -0
  57. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/spcs.py +0 -0
  58. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/table_references.py +0 -0
  59. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/datamasque/client/users.py +0 -0
  60. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/Makefile +0 -0
  61. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/client.models.rst +0 -0
  62. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/client.rst +0 -0
  63. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/conf.py +0 -0
  64. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/contributing.rst +0 -0
  65. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/history.rst +0 -0
  66. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/index.rst +0 -0
  67. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/installation.rst +0 -0
  68. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/make.bat +0 -0
  69. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/modules.rst +0 -0
  70. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/readme.rst +0 -0
  71. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/docs/usage.rst +0 -0
  72. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/__init__.py +0 -0
  73. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/conftest.py +0 -0
  74. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_base.py +0 -0
  75. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_discovery.py +0 -0
  76. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_discovery_config_libraries.py +0 -0
  77. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_discovery_configs.py +0 -0
  78. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_files.py +0 -0
  79. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_ifm.py +0 -0
  80. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_license.py +0 -0
  81. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_pagination.py +0 -0
  82. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_rg_configs.py +0 -0
  83. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_ruleset_library.py +0 -0
  84. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_rulesets.py +0 -0
  85. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_runs.py +0 -0
  86. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_safe_data_preview.py +0 -0
  87. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_settings.py +0 -0
  88. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_spcs.py +0 -0
  89. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_table_references.py +0 -0
  90. {datamasque_python-1.4.0 → datamasque_python-1.5.0}/tests/test_users.py +0 -0
@@ -2,6 +2,40 @@
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
+
5
39
  1.4.0 (2026-09-25)
6
40
  ------------------
7
41
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datamasque-python
3
- Version: 1.4.0
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,13 +39,15 @@ from datamasque.client.models.connection import (
38
39
  DocumentDbConnectionConfig,
39
40
  DynamoConnectionConfig,
40
41
  FileConnectionConfig,
42
+ GcsConnectionConfig,
41
43
  LicenseLock,
42
44
  MongoConnectionConfig,
43
45
  MountedShareConnectionConfig,
44
46
  MssqlLinkedServerConnectionConfig,
45
47
  S3ConnectionConfig,
48
+ SalesforceConnectionConfig,
49
+ SapAseConnectionConfig,
46
50
  SnowflakeConnectionConfig,
47
- SnowflakeStageLocation,
48
51
  SseConfig,
49
52
  SseSelection,
50
53
  )
@@ -179,6 +182,7 @@ __all__ = [
179
182
  "AzureConnectionConfig",
180
183
  "BooleanPreview",
181
184
  "BooleanStatistics",
185
+ "CassandraConnectionConfig",
182
186
  "ColumnKind",
183
187
  "ColumnPreview",
184
188
  "CommonStatistics",
@@ -226,6 +230,7 @@ __all__ = [
226
230
  "FileRulesetGenerationWithRGConfigRequest",
227
231
  "FirstCharsStatistics",
228
232
  "ForeignKeyRef",
233
+ "GcsConnectionConfig",
229
234
  "GitSnapshot",
230
235
  "HashColumnsTableConfig",
231
236
  "IfmAuthError",
@@ -287,6 +292,8 @@ __all__ = [
287
292
  "S3ConnectionConfig",
288
293
  "SafeDataPreview",
289
294
  "SafeDataPreviewOptions",
295
+ "SalesforceConnectionConfig",
296
+ "SapAseConnectionConfig",
290
297
  "SchemaDiscoveryColumn",
291
298
  "SchemaDiscoveryFromConfigRequest",
292
299
  "SchemaDiscoveryPage",
@@ -298,7 +305,6 @@ __all__ = [
298
305
  "SelectedFileData",
299
306
  "SnowflakeConnectionConfig",
300
307
  "SnowflakeKeyFile",
301
- "SnowflakeStageLocation",
302
308
  "SseConfig",
303
309
  "SseSelection",
304
310
  "SslZipFile",
@@ -51,15 +51,9 @@ class DatabaseType(Enum):
51
51
  databricks = "databricks"
52
52
  informix = "informix"
53
53
  saphana = "saphana"
54
-
55
-
56
- class SnowflakeStageLocation(str, Enum):
57
- """Storage backend for a Snowflake connection's external stage."""
58
-
59
- local = "local" # Not supported for production use
60
- aws_s3 = "aws_s3"
61
- azure_blob_storage = "azure_blob_storage"
62
- 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"
63
57
 
64
58
 
65
59
  class SseSelection(Enum):
@@ -285,6 +279,19 @@ class CosmosDbConnectionConfig(MongoConnectionConfig):
285
279
  return DatabaseType.cosmosdb
286
280
 
287
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
+
288
295
  class SnowflakeConnectionConfig(ConnectionConfig):
289
296
  """
290
297
  Connection configuration for a Snowflake database.
@@ -294,14 +301,14 @@ class SnowflakeConnectionConfig(ConnectionConfig):
294
301
  """
295
302
 
296
303
  database: str
297
- # Optional because DataMasque-in-SPCS connections leave these unset: the agent uses the
298
- # container's OAuth token + SNOWFLAKE_HOST/SNOWFLAKE_ACCOUNT env and the app-owned QUERY_WAREHOUSE,
299
- # so user/account/storage-integration/warehouse are null for stage_location=spcs. Mirrors the app's
300
- # canonical model (agent .../schemas/connection/connection.py), which types these `| None = None`.
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`.
301
309
  user: Optional[str] = None
302
310
  snowflake_account_id: Optional[str] = None
303
311
  snowflake_warehouse: Optional[str] = None
304
- snowflake_storage_integration_name: Optional[str] = None
305
312
  host: str = ""
306
313
  port: Optional[int] = None
307
314
  db_schema: Optional[str] = Field(default=None, alias="schema")
@@ -310,12 +317,6 @@ class SnowflakeConnectionConfig(ConnectionConfig):
310
317
  password: Optional[str] = None
311
318
  snowflake_private_key: Optional[FileId] = None
312
319
  snowflake_private_key_passphrase: Optional[str] = None
313
- snowflake_stage_location: Optional[SnowflakeStageLocation] = None
314
- s3_bucket_name: Optional[str] = None
315
- iam_role_arn: Optional[str] = None
316
- snowflake_azure_container_name: Optional[str] = None
317
- snowflake_azure_connection_string: Optional[str] = None
318
- snowflake_azure_connection_string_encrypted: Optional[str] = None
319
320
 
320
321
  mask_type: Literal["database"] = "database"
321
322
  db_type: Literal["snowflake"] = "snowflake"
@@ -338,9 +339,9 @@ class SnowflakeConnectionConfig(ConnectionConfig):
338
339
 
339
340
  @model_validator(mode="before")
340
341
  @classmethod
341
- def _strip_encrypted_password(cls, data: dict) -> dict:
342
+ def _strip_server_only_fields(cls, data: dict) -> dict:
342
343
  if isinstance(data, dict):
343
- for key in ("password_encrypted", "dbpassword"):
344
+ for key in ("password_encrypted", "dbpassword", *_SNOWFLAKE_EXTERNAL_STAGE_KEYS):
344
345
  data.pop(key, None)
345
346
  return data
346
347
 
@@ -366,7 +367,8 @@ class DatabaseConnectionConfig(ConnectionConfig):
366
367
  Connection configuration for a SQL database.
367
368
 
368
369
  Use `DynamoConnectionConfig` for DynamoDB, `SnowflakeConnectionConfig` for Snowflake,
369
- and `MongoConnectionConfig` for MongoDB.
370
+ `MongoConnectionConfig` for MongoDB, `CassandraConnectionConfig` for Apache Cassandra,
371
+ `SapAseConnectionConfig` for SAP ASE (Sybase) and `SalesforceConnectionConfig` for Salesforce.
370
372
  """
371
373
 
372
374
  host: str
@@ -401,6 +403,12 @@ class DatabaseConnectionConfig(ConnectionConfig):
401
403
  raise ValueError("For Azure Cosmos DB, use the CosmosDbConnectionConfig class instead")
402
404
  if self.database_type is DatabaseType.databricks:
403
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")
404
412
  return self
405
413
 
406
414
  mask_type: Literal["database"] = "database"
@@ -508,6 +516,29 @@ class AzureConnectionConfig(FileConnectionConfig):
508
516
  return data
509
517
 
510
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
+
511
542
  class MountedShareConnectionConfig(FileConnectionConfig):
512
543
  """Connection configuration for a mounted file share."""
513
544
 
@@ -540,9 +571,149 @@ class DatabricksConnectionConfig(ConnectionConfig):
540
571
  return data
541
572
 
542
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
+
543
713
  FILE_TYPE_MAP: dict[str, type[FileConnectionConfig]] = {
544
714
  "s3_connection": S3ConnectionConfig,
545
715
  "azure_blob_connection": AzureConnectionConfig,
716
+ "gcs_connection": GcsConnectionConfig,
546
717
  "mounted_share_connection": MountedShareConnectionConfig,
547
718
  }
548
719
 
@@ -554,6 +725,9 @@ DB_TYPE_MAP: dict[str, type[ConnectionConfig]] = {
554
725
  DatabaseType.snowflake.value: SnowflakeConnectionConfig,
555
726
  DatabaseType.mssql_linked.value: MssqlLinkedServerConnectionConfig,
556
727
  DatabaseType.databricks.value: DatabricksConnectionConfig,
728
+ DatabaseType.cassandra.value: CassandraConnectionConfig,
729
+ DatabaseType.sap_ase.value: SapAseConnectionConfig,
730
+ DatabaseType.salesforce.value: SalesforceConnectionConfig,
557
731
  # others use the default `DatabaseConnectionConfig`
558
732
  }
559
733
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datamasque-python"
3
- version = "1.4.0"
3
+ version = "1.5.0"
4
4
  description = "Official Python client for the DataMasque data-masking API."
5
5
  authors = [
6
6
  { name = "DataMasque Ltd" },
@@ -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 snowflake_connection_config_s3():
119
+ def snowflake_connection_config():
121
120
  return SnowflakeConnectionConfig(
122
- name="snowflake_s3",
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
  )