datamasque-python 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,229 @@
1
+ """Typed request and response shapes for schema-discovery and ruleset-generation endpoints."""
2
+
3
+ from typing import Any, Optional, Union
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
6
+
7
+ from datamasque.client.models.connection import ConnectionConfig, ConnectionId, unwrap_connection_id
8
+ from datamasque.client.models.data_selection import HashColumnsTableConfig, Locator, UserSelection
9
+ from datamasque.client.models.pagination import Page
10
+
11
+
12
+ class InDataDiscoveryRule(BaseModel):
13
+ """A single rule for in-data discovery."""
14
+
15
+ model_config = ConfigDict(extra="forbid")
16
+
17
+ name: Optional[str] = None
18
+ pattern: str
19
+
20
+
21
+ class InDataDiscoveryConfig(BaseModel):
22
+ """In-data discovery configuration nested under `SchemaDiscoveryRequest.in_data_discovery`."""
23
+
24
+ model_config = ConfigDict(extra="forbid")
25
+
26
+ enabled: Optional[bool] = None
27
+ row_sample_size: Optional[int] = None
28
+ custom_rules: Optional[list[InDataDiscoveryRule]] = None
29
+ non_sensitive_rules: Optional[list[InDataDiscoveryRule]] = None
30
+ force: Optional[bool] = None
31
+
32
+
33
+ class SchemaDiscoveryRequest(BaseModel):
34
+ """
35
+ Request body for `POST /api/schema-discovery/`.
36
+
37
+ `connection` accepts either a `ConnectionId` or a full `ConnectionConfig` returned by an earlier client call.
38
+ Every other field uses the server's default value when omitted.
39
+ """
40
+
41
+ model_config = ConfigDict(extra="forbid")
42
+
43
+ connection: Union[ConnectionId, ConnectionConfig]
44
+ custom_keywords: list[str] = Field(default_factory=list)
45
+ ignored_keywords: list[str] = Field(default_factory=list)
46
+ schemas: list[str] = Field(default_factory=list)
47
+ in_data_discovery: Optional[InDataDiscoveryConfig] = None
48
+ disable_built_in_keywords: bool = False
49
+ disable_global_custom_keywords: bool = False
50
+ disable_global_ignored_keywords: bool = False
51
+
52
+ @field_validator("connection", mode="before")
53
+ @classmethod
54
+ def _unwrap_connection(cls, value: Any) -> Any:
55
+ return unwrap_connection_id(value)
56
+
57
+
58
+ class RulesetGenerationRequest(BaseModel):
59
+ """
60
+ Request body for `POST /api/generate-ruleset/v2/`.
61
+
62
+ `connection` accepts either a `ConnectionId` or a full `ConnectionConfig` returned by an earlier client call.
63
+ `selected_columns` is the same nested `schema -> table -> [column, ...]` mapping
64
+ used by `SelectedColumns.columns`,
65
+ and `hash_columns` follows the `HashColumnsTableConfig` shape.
66
+ """
67
+
68
+ model_config = ConfigDict(extra="forbid")
69
+
70
+ connection: Union[ConnectionId, ConnectionConfig]
71
+ selected_columns: dict[str, dict[str, list[str]]]
72
+ hash_columns: Optional[dict[str, dict[str, HashColumnsTableConfig]]] = None
73
+
74
+ @field_validator("connection", mode="before")
75
+ @classmethod
76
+ def _unwrap_connection(cls, value: Any) -> Any:
77
+ return unwrap_connection_id(value)
78
+
79
+
80
+ class FileRulesetGenerationRequest(BaseModel):
81
+ """
82
+ Request body for `POST /api/generate-file-ruleset/`.
83
+
84
+ `connection` accepts either a `ConnectionId` or a full `ConnectionConfig` returned by an earlier client call.
85
+ """
86
+
87
+ model_config = ConfigDict(extra="forbid")
88
+
89
+ connection: Union[ConnectionId, ConnectionConfig]
90
+ selected_data: list[UserSelection]
91
+
92
+ @field_validator("connection", mode="before")
93
+ @classmethod
94
+ def _unwrap_connection(cls, value: Any) -> Any:
95
+ return unwrap_connection_id(value)
96
+
97
+
98
+ class DiscoveryMatch(BaseModel):
99
+ """A single match found by schema or file discovery."""
100
+
101
+ model_config = ConfigDict(extra="allow")
102
+
103
+ label: str
104
+ categories: list[str]
105
+ flagged_by: str
106
+ description: str
107
+ hit_ratio: Optional[int] = None # None for metadata matches, percentage 0-100 for IDD matches.
108
+
109
+
110
+ class ForeignKeyRef(BaseModel):
111
+ """A foreign key declared on a column, pointing to another column it references."""
112
+
113
+ model_config = ConfigDict(extra="allow")
114
+
115
+ name: str
116
+ referenced_column: str # Dotted path: "schema.table.column".
117
+
118
+
119
+ class ReferencingForeignKey(BaseModel):
120
+ """A foreign key declared on another column that points *at* this column."""
121
+
122
+ model_config = ConfigDict(extra="allow")
123
+
124
+ name: str
125
+ referencing_column: str # Dotted path: "schema.table.column".
126
+
127
+
128
+ class SchemaDiscoveryColumn(BaseModel):
129
+ """Column-level data in a schema discovery result."""
130
+
131
+ model_config = ConfigDict(extra="allow")
132
+
133
+ data_type: Optional[str] = None
134
+ max_length: Optional[int] = None
135
+ foreign_keys: list[ForeignKeyRef]
136
+ discovery_matches: list[DiscoveryMatch]
137
+ numeric_precision: Optional[int] = None
138
+ numeric_scale: Optional[int] = None
139
+ constraint_columns: list[str]
140
+ pk_constraint_name: Optional[str] = None
141
+ uk_constraint_name: Optional[str] = None
142
+ unique_index_names: list[str]
143
+ referencing_foreign_keys: list[ReferencingForeignKey]
144
+ constraint: str # Primary or Unique, or empty string if column does not participate in a PK/UK
145
+
146
+
147
+ class SchemaDiscoveryResult(BaseModel):
148
+ """A single row in the v2 schema discovery results."""
149
+
150
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
151
+
152
+ id: int
153
+ column: str
154
+ table: str
155
+ schema_name: Optional[str] = Field(default=None, alias="schema") # "schema" is a reserved word in Pydantic
156
+ data: SchemaDiscoveryColumn
157
+
158
+
159
+ class ConstraintColumns(BaseModel):
160
+ """A constraint's column list in table metadata."""
161
+
162
+ model_config = ConfigDict(extra="allow")
163
+
164
+ columns: list[str]
165
+
166
+
167
+ class TableConstraints(BaseModel):
168
+ """Constraint metadata for a single table."""
169
+
170
+ model_config = ConfigDict(extra="allow")
171
+
172
+ primary_keys: Optional[list[ConstraintColumns]] = None
173
+ unique_keys: Optional[list[ConstraintColumns]] = None
174
+ foreign_keys: Optional[list[ConstraintColumns]] = None
175
+
176
+
177
+ class SchemaDiscoveryPage(Page[SchemaDiscoveryResult]):
178
+ """
179
+ Admin-server envelope for `GET /api/schema-discovery/v2/{run_id}/`.
180
+
181
+ Extends the standard `Page` with `table_metadata`.
182
+ """
183
+
184
+ table_metadata: Optional[dict[str, dict[str, TableConstraints]]] = None
185
+
186
+
187
+ class FileDiscoveryMatch(BaseModel):
188
+ """A single match in a file discovery locator."""
189
+
190
+ model_config = ConfigDict(extra="allow")
191
+
192
+ categories: Optional[list[str]] = None
193
+ flagged_by: Optional[str] = None
194
+ description: Optional[str] = None
195
+ label: Optional[str] = None
196
+ hit_ratio: Optional[int] = None
197
+
198
+
199
+ class FileDiscoveryLocatorResult(BaseModel):
200
+ """A locator (column/path) within a discovered file."""
201
+
202
+ model_config = ConfigDict(extra="allow")
203
+
204
+ locator: Optional[Locator] = None
205
+ matches: Optional[list[FileDiscoveryMatch]] = None
206
+ data_types: Optional[list[str]] = None
207
+
208
+
209
+ class FileDiscoveryFile(BaseModel):
210
+ """A file entry in a file discovery result."""
211
+
212
+ model_config = ConfigDict(extra="allow")
213
+
214
+ path: Optional[str] = None
215
+ file_type: Optional[str] = None
216
+ delimiter: Optional[str] = None
217
+ encoding: Optional[str] = None
218
+
219
+
220
+ class FileDiscoveryResult(BaseModel):
221
+ """A single record from `GET /api/runs/{run_id}/file-discovery-results/`."""
222
+
223
+ model_config = ConfigDict(extra="allow")
224
+
225
+ id: Optional[int] = None
226
+ connection: Optional[Any] = None
227
+ file_type: Optional[str] = None
228
+ files: Optional[list[FileDiscoveryFile]] = None
229
+ results: Optional[list[FileDiscoveryLocatorResult]] = None
@@ -0,0 +1,39 @@
1
+ from typing import Callable, Optional
2
+
3
+ from pydantic import BaseModel, ConfigDict, model_validator
4
+
5
+ from datamasque.client.exceptions import DataMasqueUserError
6
+
7
+
8
+ class DataMasqueInstanceConfig(BaseModel):
9
+ """
10
+ Connection configuration for `DataMasqueClient`.
11
+
12
+ `base_url` is the root URL of the DataMasque admin server
13
+ (e.g. `https://datamasque.example.com/`).
14
+ Set `verify_ssl=False` to skip TLS certificate verification
15
+ (only use this with a self-signed certificate;
16
+ do not disable it otherwise).
17
+ Exactly one of `password` or `token_source` must be set.
18
+ `token_source` is a user-supplied callable that returns the bare API token string —
19
+ the hex value returned by `POST /api/auth/token/login/`;
20
+ the client prepends it with `Token ` when sending the `Authorization` header.
21
+ The client calls `token_source` on each authentication attempt,
22
+ so the callable is free to fetch and refresh tokens out-of-band (e.g. from a secrets manager).
23
+ """
24
+
25
+ model_config = ConfigDict(arbitrary_types_allowed=True)
26
+
27
+ base_url: str
28
+ username: str
29
+ password: Optional[str] = None
30
+ verify_ssl: bool = True
31
+ token_source: Optional[Callable[[], str]] = None
32
+
33
+ @model_validator(mode="after")
34
+ def _validate_auth_source(self) -> "DataMasqueInstanceConfig":
35
+ if (self.password is None) == (self.token_source is None):
36
+ raise DataMasqueUserError(
37
+ "Exactly one of `password` or `token_source` must be provided to `DataMasqueInstanceConfig`."
38
+ )
39
+ return self
@@ -0,0 +1,89 @@
1
+ from abc import abstractmethod
2
+ from datetime import datetime
3
+ from typing import NewType, Optional
4
+
5
+ from pydantic import BaseModel, ConfigDict, model_validator
6
+
7
+ FileId = NewType("FileId", str)
8
+
9
+
10
+ class DataMasqueFile(BaseModel):
11
+ """Base class for the concrete file types (`SeedFile`, `OracleWalletFile`, `SslZipFile`, `SnowflakeKeyFile`)."""
12
+
13
+ model_config = ConfigDict(extra="allow")
14
+
15
+ name: str
16
+ created_date: datetime
17
+ modified_date: Optional[datetime] = None
18
+ id: Optional[FileId] = None
19
+
20
+ @model_validator(mode="before")
21
+ @classmethod
22
+ def _promote_filename(cls, data: dict) -> dict:
23
+ """The API sometimes returns `filename` instead of `name`."""
24
+ if isinstance(data, dict):
25
+ if "filename" in data and "name" not in data:
26
+ data["name"] = data["filename"]
27
+ return data
28
+
29
+ @classmethod
30
+ @abstractmethod
31
+ def get_url(cls) -> str:
32
+ """Returns the API URL path for files of this type."""
33
+
34
+ raise NotImplementedError # pragma: no cover
35
+
36
+ @classmethod
37
+ @abstractmethod
38
+ def get_content_param_name(cls) -> str:
39
+ """Returns the multipart form field name used when uploading files of this type."""
40
+
41
+ raise NotImplementedError # pragma: no cover
42
+
43
+
44
+ class SeedFile(DataMasqueFile):
45
+ """Represents a seed file (CSV file)."""
46
+
47
+ @classmethod
48
+ def get_url(cls) -> str:
49
+ return "api/seeds/"
50
+
51
+ @classmethod
52
+ def get_content_param_name(cls) -> str:
53
+ return "seed_file"
54
+
55
+
56
+ class OracleWalletFile(DataMasqueFile):
57
+ """Represents an Oracle wallet file (ZIP file)."""
58
+
59
+ @classmethod
60
+ def get_url(cls) -> str:
61
+ return "api/oracle-wallets/"
62
+
63
+ @classmethod
64
+ def get_content_param_name(cls) -> str:
65
+ return "zip_archive"
66
+
67
+
68
+ class SslZipFile(DataMasqueFile):
69
+ """Represents a ZIP file of SSL certificates used to establish secure database connections."""
70
+
71
+ @classmethod
72
+ def get_url(cls) -> str:
73
+ return "api/connection-filesets/"
74
+
75
+ @classmethod
76
+ def get_content_param_name(cls) -> str:
77
+ return "zip_archive"
78
+
79
+
80
+ class SnowflakeKeyFile(DataMasqueFile):
81
+ """Represents a private SSH key file for Snowflake connections."""
82
+
83
+ @classmethod
84
+ def get_url(cls) -> str:
85
+ return "api/files/snowflake-keys/"
86
+
87
+ @classmethod
88
+ def get_content_param_name(cls) -> str:
89
+ return "key_file"
@@ -0,0 +1,177 @@
1
+ """Typed request and response shapes for the IFM (in-flight masking) HTTP API."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Callable, Optional
5
+
6
+ from pydantic import BaseModel, ConfigDict, model_validator
7
+
8
+ from datamasque.client.exceptions import DataMasqueUserError
9
+
10
+
11
+ class RulesetPlanOptions(BaseModel):
12
+ """
13
+ Server-defined defaults applied when a mask request omits the corresponding fields.
14
+
15
+ All keys are optional;
16
+ callers can supply any subset (or none) and the IFM server fills in remaining defaults.
17
+ """
18
+
19
+ model_config = ConfigDict(extra="forbid")
20
+
21
+ enabled: Optional[bool] = None
22
+ # NB: Encoding and charset are not currently implemented for IFM.
23
+ # These fields are here just to ensure we can round-trip a `RulesetPlan` object.
24
+ default_encoding: Optional[str] = None
25
+ default_charset: Optional[str] = None
26
+ default_log_level: Optional[str] = None
27
+
28
+
29
+ class IfmLog(BaseModel):
30
+ """A single log entry produced by IFM during a mask call or a ruleset-plan validation."""
31
+
32
+ model_config = ConfigDict(extra="allow")
33
+
34
+ log_level: str
35
+ timestamp: str
36
+ message: str
37
+
38
+
39
+ class IfmRulesetPlanRef(BaseModel):
40
+ """Reference to a ruleset plan embedded in a mask response."""
41
+
42
+ model_config = ConfigDict(extra="allow")
43
+
44
+ name: str
45
+ serial: int
46
+
47
+
48
+ class RulesetPlan(BaseModel):
49
+ """
50
+ Unified model for IFM ruleset plans.
51
+
52
+ Collapses the list/detail/create/update response shapes into one model
53
+ with optional fields for parts that differ by endpoint.
54
+ """
55
+
56
+ model_config = ConfigDict(extra="allow")
57
+
58
+ name: str
59
+ serial: int
60
+ created_time: datetime
61
+ modified_time: datetime
62
+ options: RulesetPlanOptions
63
+ ruleset_yaml: Optional[str] = None
64
+ logs: Optional[list[IfmLog]] = None
65
+ url: Optional[str] = None
66
+
67
+
68
+ class RulesetPlanCreateRequest(BaseModel):
69
+ """Request body for `POST /ifm/ruleset-plans/`."""
70
+
71
+ model_config = ConfigDict(extra="forbid")
72
+
73
+ name: str
74
+ ruleset_yaml: str
75
+ options: Optional[RulesetPlanOptions] = None
76
+
77
+
78
+ class RulesetPlanUpdateRequest(BaseModel):
79
+ """Request body for `PUT /ifm/ruleset-plans/{name}/`."""
80
+
81
+ model_config = ConfigDict(extra="forbid")
82
+
83
+ ruleset_yaml: str
84
+ options: Optional[RulesetPlanOptions] = None
85
+
86
+
87
+ class RulesetPlanPartialUpdateRequest(BaseModel):
88
+ """Request body for `PATCH /ifm/ruleset-plans/{name}/` — every field is optional."""
89
+
90
+ model_config = ConfigDict(extra="forbid")
91
+
92
+ ruleset_yaml: Optional[str] = None
93
+ options: Optional[RulesetPlanOptions] = None
94
+
95
+
96
+ class IfmMaskRequest(BaseModel):
97
+ """
98
+ Request body for `POST /ruleset-plans/{name}/mask/`.
99
+
100
+ `data` is the list of records to be masked;
101
+ every other field overrides server defaults configured on the plan.
102
+ """
103
+
104
+ model_config = ConfigDict(extra="forbid")
105
+
106
+ data: list[Any]
107
+ disable_instance_secret: Optional[bool] = None
108
+ run_secret: Optional[str] = None
109
+ hash_values: Optional[Any] = None
110
+ log_level: Optional[str] = None
111
+ request_id: Optional[str] = None
112
+ ai_engine_url: Optional[str] = None
113
+
114
+
115
+ class IfmMaskResult(BaseModel):
116
+ """
117
+ Response shape for `POST /ruleset-plans/{name}/mask/`.
118
+
119
+ `success` is populated by the client based on the HTTP status the server returned:
120
+
121
+ - `True` — masking completed;
122
+ `data` carries the masked records (possibly an empty list if the request had no input).
123
+ - `False` — the server rejected the request with a soft failure
124
+ (e.g. a masking function received an unsupported value type);
125
+ `data` is omitted and details surface in `logs`.
126
+
127
+ Hard failures (plan not found, auth, transport) still raise rather than producing an `IfmMaskResult`.
128
+ """
129
+
130
+ model_config = ConfigDict(extra="allow")
131
+
132
+ success: bool
133
+ request_id: Optional[str] = None
134
+ ruleset_plan: Optional[IfmRulesetPlanRef] = None
135
+ logs: Optional[list[IfmLog]] = None
136
+ data: Optional[list[Any]] = None
137
+
138
+
139
+ class IfmTokenInfo(BaseModel):
140
+ """Response body for `GET /verify-token/` — the list of scopes granted to the current JWT."""
141
+
142
+ model_config = ConfigDict(extra="allow")
143
+
144
+ scopes: list[str]
145
+
146
+
147
+ class DataMasqueIfmInstanceConfig(BaseModel):
148
+ """
149
+ Connection configuration for `DataMasqueIfmClient`.
150
+
151
+ `admin_server_base_url` is where JWTs are obtained and refreshed;
152
+ `ifm_base_url` is where the IFM API itself lives
153
+ (typically a separate hostname or the admin server with `/ifm` prefix).
154
+ Exactly one of `password` or `token_source` must be set.
155
+ `token_source` is a user-supplied callable that returns the bare JWT access token string —
156
+ the value issued by the admin server's `/api/auth/jwt/login/` endpoint;
157
+ the client prepends it with `Bearer ` when sending the `Authorization` header.
158
+ The client calls `token_source` on each authentication and refresh,
159
+ so the callable is free to fetch and refresh tokens out-of-band (e.g. from a secrets manager).
160
+ """
161
+
162
+ model_config = ConfigDict(arbitrary_types_allowed=True)
163
+
164
+ admin_server_base_url: str
165
+ ifm_base_url: str
166
+ username: str
167
+ password: Optional[str] = None
168
+ verify_ssl: bool = True
169
+ token_source: Optional[Callable[[], str]] = None
170
+
171
+ @model_validator(mode="after")
172
+ def _validate_auth_source(self) -> "DataMasqueIfmInstanceConfig":
173
+ if (self.password is None) == (self.token_source is None):
174
+ raise DataMasqueUserError(
175
+ "Exactly one of `password` or `token_source` must be provided to `DataMasqueIfmInstanceConfig`."
176
+ )
177
+ return self
@@ -0,0 +1,60 @@
1
+ """Typed response shape for the license endpoint."""
2
+
3
+ from datetime import datetime
4
+ from typing import Optional
5
+
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+
9
+ class SwitchableLicenseMetadata(BaseModel):
10
+ """Metadata for switchable license management (AWS Marketplace, etc.)."""
11
+
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ can_switch_license_source: Optional[bool] = None
15
+ license_source: Optional[str] = None
16
+ license_select_time: Optional[datetime] = None
17
+ aws_account_number: Optional[str] = None
18
+ last_checkout_success_time: Optional[datetime] = None
19
+ last_checkout_success_type: Optional[str] = None
20
+ last_checkout_error: Optional[str] = None
21
+ last_checkout_license_arn: Optional[str] = None
22
+ last_checkout_product_name: Optional[str] = None
23
+ last_checkout_contract_expiry: Optional[datetime] = None
24
+ last_checkout_agreement_id: Optional[str] = None
25
+ last_checkout_agreement_url: Optional[str] = None
26
+ checkout_mode: Optional[str] = None
27
+ selected_product_sku: Optional[str] = None
28
+ allow_fallback: Optional[bool] = None
29
+ last_checkout_success_license_count: Optional[int] = None
30
+ iam_role_arn: Optional[str] = None
31
+
32
+
33
+ class LicenseInfo(BaseModel):
34
+ """
35
+ License information returned by `GET /api/license/`.
36
+
37
+ Core fields (`uuid`, `name`, `type`, `is_expired`, `uploadable`)
38
+ are always present in the server response.
39
+ Other fields vary by license type and server version.
40
+ """
41
+
42
+ model_config = ConfigDict(extra="allow")
43
+
44
+ uuid: str
45
+ name: str
46
+ type: str
47
+ is_expired: bool
48
+ uploadable: bool
49
+ version: Optional[str] = None
50
+ raw_type: Optional[str] = None
51
+ expiry_date: Optional[datetime] = None
52
+ quota_tb: Optional[float] = None
53
+ maximum_node_count: Optional[int] = None
54
+ row_limit: Optional[int] = None
55
+ platform_name: Optional[str] = None
56
+ platform_code: Optional[str] = None
57
+ days_until_expiry: Optional[int] = None
58
+ is_contract_product: Optional[bool] = None
59
+ contract_license_type: Optional[str] = None
60
+ switchable_license_metadata: Optional[SwitchableLicenseMetadata] = None
@@ -0,0 +1,29 @@
1
+ """Pagination envelope models matching the DataMasque admin-server and IFM list-endpoint response shapes."""
2
+
3
+ from typing import Generic, Optional, TypeVar
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class Page(BaseModel, Generic[T]):
11
+ """Admin-server paginated response envelope."""
12
+
13
+ model_config = ConfigDict(extra="allow")
14
+
15
+ count: int
16
+ next: Optional[str] = None
17
+ previous: Optional[str] = None
18
+ results: list[T]
19
+
20
+
21
+ class IfmPage(BaseModel, Generic[T]):
22
+ """IFM paginated response envelope."""
23
+
24
+ model_config = ConfigDict(extra="allow")
25
+
26
+ items: list[T]
27
+ total: int
28
+ limit: int
29
+ offset: int
@@ -0,0 +1,45 @@
1
+ import enum
2
+ from typing import Any, NewType, Optional
3
+
4
+ from pydantic import BaseModel, ConfigDict, Field
5
+
6
+ from datamasque.client.models.status import ValidationStatus
7
+
8
+ RulesetId = NewType("RulesetId", str)
9
+
10
+
11
+ def unwrap_ruleset_id(value: Any) -> Any:
12
+ """
13
+ Coerce a `Ruleset` to its `id`; pass other values through unchanged.
14
+
15
+ Used by request-model validators that accept either a `RulesetId`
16
+ or a full `Ruleset` for user convenience.
17
+ Raises `ValueError` if the ruleset has no `id`
18
+ (i.e. the caller hasn't yet created it on the server).
19
+ """
20
+
21
+ if isinstance(value, Ruleset):
22
+ if value.id is None:
23
+ raise ValueError("Ruleset has not been created yet (id is None)")
24
+ return value.id
25
+
26
+ return value
27
+
28
+
29
+ class RulesetType(enum.Enum):
30
+ """Ruleset type (database masking or file masking)."""
31
+
32
+ file = "file"
33
+ database = "database"
34
+
35
+
36
+ class Ruleset(BaseModel):
37
+ """Represents a ruleset."""
38
+
39
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
40
+
41
+ name: str
42
+ yaml: str = Field(default="", alias="config_yaml")
43
+ ruleset_type: RulesetType = Field(default=RulesetType.database, alias="mask_type")
44
+ id: Optional[RulesetId] = None
45
+ is_valid: Optional[ValidationStatus] = None
@@ -0,0 +1,22 @@
1
+ from datetime import datetime
2
+ from typing import NewType, Optional
3
+
4
+ from pydantic import BaseModel, ConfigDict, Field
5
+
6
+ from datamasque.client.models.status import ValidationStatus
7
+
8
+ RulesetLibraryId = NewType("RulesetLibraryId", str)
9
+
10
+
11
+ class RulesetLibrary(BaseModel):
12
+ """Represents a ruleset library."""
13
+
14
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
15
+
16
+ name: str
17
+ namespace: str = ""
18
+ yaml: Optional[str] = Field(default=None, alias="config_yaml")
19
+ id: Optional[RulesetLibraryId] = None
20
+ is_valid: Optional[ValidationStatus] = None
21
+ created: Optional[datetime] = None
22
+ modified: Optional[datetime] = None