brynq-sdk-brynq 4.2.6.dev0__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.

Potentially problematic release.


This version of brynq-sdk-brynq might be problematic. Click here for more details.

@@ -0,0 +1,419 @@
1
+ """
2
+ Scenarios schemas for an active template interface in BrynQ API (2.0.0).
3
+ By default, this endpoint returns the most recent live version. If the latest version is not yet published (still a draft), the draft version is returned instead.
4
+
5
+ These schemas represent the exact structure of the response schema (application/json).
6
+
7
+ For parsed/processed scenario models and business logic, see brynq_sdk_brynq.scenarios.
8
+ """
9
+
10
+ from datetime import datetime
11
+ from enum import Enum
12
+ from typing import Any, Dict, List, Literal, Optional, Union, TypedDict
13
+ from uuid import UUID
14
+
15
+ from pydantic import BaseModel, ConfigDict, Field, RootModel, EmailStr, field_validator
16
+ from pydantic.types import AwareDatetime
17
+ from typing_extensions import Annotated
18
+
19
+
20
+ # ============================================================================
21
+ # 1. Type Aliases & Enums
22
+ # ============================================================================
23
+
24
+ class FieldType(str, Enum):
25
+ """Enumeration of field type.
26
+
27
+ """
28
+ CUSTOM = "CUSTOM"
29
+ LIBRARY = "LIBRARY"
30
+ FIXED = "FIXED"
31
+ EMPTY = "EMPTY"
32
+ CONFIGURATION = "CONFIGURATION"
33
+
34
+
35
+ class SystemType(str, Enum):
36
+ """Enumeration of system type.
37
+
38
+ """
39
+ SOURCE = "source"
40
+ TARGET = "target"
41
+
42
+
43
+ class RelationType(str, Enum):
44
+ """Cardinality of the mapping.
45
+
46
+ Defines if a record represents a simple 1:1 map or a complex N:N explosion.
47
+ """
48
+ ONE_TO_ONE = "one_to_one"
49
+ ONE_TO_MANY = "one_to_many"
50
+ MANY_TO_ONE = "many_to_one"
51
+ MANY_TO_MANY = "many_to_many"
52
+
53
+
54
+ class ConfigurationType(str, Enum):
55
+ """Enumeration of configuration field types.
56
+
57
+ Defines the type of configuration field used in ConfigFieldValues.
58
+ """
59
+ ATTACHMENT = "ATTACHMENT"
60
+ TEXT = "TEXT"
61
+ EMAIL = "EMAIL"
62
+ NUMBER = "NUMBER"
63
+ RICHTEXT = "RICHTEXT"
64
+ DATEPICKER = "DATEPICKER"
65
+ SELECTION = "SELECTION"
66
+
67
+
68
+ # ============================================================================
69
+ # 2. Value level API Models
70
+ # ============================================================================
71
+ # These classes define the shape of the values of the json returned by BrynQ API.
72
+
73
+ class MultiLanguageText(TypedDict):
74
+ """Multi-language text dictionary with English and Dutch labels.
75
+
76
+ Attributes:
77
+ en: English text
78
+ nl: Dutch text
79
+ """
80
+ en: str
81
+ nl: str
82
+
83
+ # VALUES
84
+ class MappingValue(BaseModel):
85
+ """Represents a single translation rule (e.g., 'CEO' -> '96').
86
+
87
+ Both input and output dictionaries use field identifier keys (UUIDs or schema.name patterns)
88
+ that need to be converted to readable field names. The values are the actual data values.
89
+
90
+ Attributes:
91
+ input: Dictionary mapping source field identifiers (UUIDs or schema.name patterns) to source values.
92
+ Example: {"work_schema-title": "CEO"} means source field "work_schema-title" has value "CEO".
93
+ The key is a field identifier (not a readable field name) and needs conversion.
94
+ output: Dictionary mapping target field identifiers (UUIDs or schema.name patterns) to target values.
95
+ Example: {"ea06ce9f-e10e-484e-bdf0-ec58087f15c5": "96"} means target field with UUID should get value "96".
96
+ The key is a UUID (not a readable field name) and needs conversion.
97
+ """
98
+ input: Dict[str, str]
99
+ output: Dict[str, str]
100
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
101
+
102
+
103
+ class CustomDataValues(BaseModel):
104
+ """Schema for custom fields from the source system.
105
+
106
+ Attributes:
107
+ uuid: Unique identifier
108
+ name: Human-readable label
109
+ technical_name: System ID (aliased as "technicalName" in API)
110
+ source: Category bucket
111
+ description: Business context
112
+ """
113
+ uuid: str = Field(..., description="Unique identifier")
114
+ name: str = Field(..., description="Human-readable label")
115
+ technical_name: str = Field(..., alias="technicalName", description="System ID")
116
+ source: str = Field(..., description="Category bucket")
117
+ description: str = Field(..., description="Business context")
118
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
119
+
120
+
121
+ class LibraryFieldValues(BaseModel):
122
+ """Schema for BrynQ Library fields.
123
+
124
+ Attributes:
125
+ id: integer identifier
126
+ uuid: str or schema field identifier (e.g., "people_schema-work.employeeIdInCompany")
127
+ required: Whether this field is required
128
+ field: Field name/identifier
129
+ field_label: Multi-language label dictionary (aliased as "fieldLabel" in API)
130
+ app_id: Application ID (aliased as "appId" in API)
131
+ category: Category metadata dictionary
132
+ """
133
+ id: Optional[int] = None
134
+ uuid: Optional[str] = None
135
+ required: Optional[bool] = None
136
+ field: Optional[str] = None
137
+ field_label: Optional[Dict[str, str]] = Field(default=None, alias="fieldLabel")
138
+ app_id: Optional[int] = Field(default=None, alias="appId")
139
+ category: Optional[Dict[str, Any]] = None
140
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
141
+
142
+ class ConfigFieldValuesSelection(BaseModel):
143
+ """Configuration field of type SELECTION.
144
+
145
+ Attributes:
146
+ uuid: str identifier
147
+ question: Multi-language question dictionary with "en" (English) and "nl" (Dutch) keys
148
+ value: List of selection options, each with "en" and "nl" keys
149
+ type: Always "SELECTION"
150
+ """
151
+ uuid: str
152
+ question: MultiLanguageText = Field(..., description="Question asked to customer.")
153
+ value: List[MultiLanguageText] = Field(..., description="List of selection options with en/nl labels")
154
+ type: Literal[ConfigurationType.SELECTION] = ConfigurationType.SELECTION
155
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
156
+
157
+
158
+ class ConfigFieldValuesText(BaseModel):
159
+ """Configuration field of type TEXT.
160
+
161
+ Attributes:
162
+ uuid: str identifier
163
+ question: Multi-language question dictionary with "en" (English) and "nl" (Dutch) keys
164
+ value: Text string response
165
+ type: Always "TEXT"
166
+ """
167
+ uuid: str
168
+ question: MultiLanguageText = Field(..., description="Question asked to customer.")
169
+ value: str = Field(..., description="Text response")
170
+ type: Literal[ConfigurationType.TEXT] = ConfigurationType.TEXT
171
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
172
+
173
+
174
+ class ConfigFieldValuesEmail(BaseModel):
175
+ """Configuration field of type EMAIL.
176
+
177
+ Attributes:
178
+ uuid: str identifier
179
+ question: Multi-language question dictionary with "en" (English) and "nl" (Dutch) keys
180
+ value: Email address string (validated as email format)
181
+ type: Always "EMAIL"
182
+ """
183
+ uuid: str
184
+ question: MultiLanguageText = Field(..., description="Question asked to customer.")
185
+ value: EmailStr = Field(..., description="Email address response")
186
+ type: Literal[ConfigurationType.EMAIL] = ConfigurationType.EMAIL
187
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
188
+
189
+
190
+ class ConfigFieldValuesNumber(BaseModel):
191
+ """Configuration field of type NUMBER.
192
+
193
+ Attributes:
194
+ uuid: str identifier
195
+ question: Multi-language question dictionary with "en" (English) and "nl" (Dutch) keys
196
+ value: Numeric value (integer or float)
197
+ type: Always "NUMBER"
198
+ """
199
+ uuid: str
200
+ question: MultiLanguageText = Field(..., description="Question asked to customer.")
201
+ value: Union[int, float] = Field(..., description="Numeric response")
202
+ type: Literal[ConfigurationType.NUMBER] = ConfigurationType.NUMBER
203
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
204
+
205
+
206
+ class ConfigFieldValuesRichtext(BaseModel):
207
+ """Configuration field of type RICHTEXT.
208
+
209
+ Attributes:
210
+ uuid: str identifier
211
+ question: Multi-language question dictionary with "en" (English) and "nl" (Dutch) keys
212
+ value: Rich text string (may include HTML elements)
213
+ type: Always "RICHTEXT"
214
+ """
215
+ uuid: str
216
+ question: MultiLanguageText = Field(..., description="Question asked to customer.")
217
+ value: str = Field(..., description="Rich text response (may include HTML)")
218
+ type: Literal[ConfigurationType.RICHTEXT] = ConfigurationType.RICHTEXT
219
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
220
+
221
+
222
+ class ConfigFieldValuesDatepicker(BaseModel):
223
+ """Configuration field of type DATEPICKER.
224
+
225
+ Handles both single dates and date ranges because the API
226
+ does not distinguish between them in the 'type' field.
227
+ """
228
+ uuid: str
229
+ question: MultiLanguageText = Field(..., description="Question asked to customer.")
230
+ value: Union[AwareDatetime, List[AwareDatetime]] = Field(
231
+ ...,
232
+ description="Single datetime or list of 2 datetimes (ISO format with timezone)"
233
+ )
234
+ type: Literal[ConfigurationType.DATEPICKER] = ConfigurationType.DATEPICKER
235
+
236
+ @field_validator('value')
237
+ @classmethod
238
+ def validate_range_length(cls, v: Union[datetime, List[datetime]]) -> Union[datetime, List[datetime]]:
239
+ """Validate list length if input is a list."""
240
+ if isinstance(v, list) and len(v) != 2:
241
+ raise ValueError(f"Date range must contain exactly 2 datetime items, got {len(v)}")
242
+ return v
243
+
244
+ model_config = ConfigDict(frozen=True, strict=False, populate_by_name=True)
245
+
246
+
247
+ class ConfigFieldValuesAttachment(BaseModel):
248
+ """Configuration field of type ATTACHMENT.
249
+
250
+ Attributes:
251
+ uuid: str identifier
252
+ question: Multi-language question dictionary with "en" (English) and "nl" (Dutch) keys
253
+ value: Always None for attachment type
254
+ type: Always "ATTACHMENT"
255
+ """
256
+ uuid: str
257
+ question: MultiLanguageText = Field(..., description="Question asked to customer.")
258
+ value: None = Field(None, description="Always None for attachment type")
259
+ type: Literal[ConfigurationType.ATTACHMENT] = ConfigurationType.ATTACHMENT
260
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
261
+
262
+
263
+ # Discriminated union for ConfigFieldValues based on type field
264
+ ConfigFieldValues = Annotated[
265
+ Union[
266
+ ConfigFieldValuesSelection,
267
+ ConfigFieldValuesText,
268
+ ConfigFieldValuesEmail,
269
+ ConfigFieldValuesNumber,
270
+ ConfigFieldValuesRichtext,
271
+ ConfigFieldValuesDatepicker,
272
+ ConfigFieldValuesAttachment,
273
+ ],
274
+ Field(discriminator="type")
275
+ ]
276
+
277
+
278
+ # ============================================================================
279
+ # 2. Field (or key) level API Models
280
+ # ============================================================================
281
+ # These classes define the shape of the fields of the json returned by BrynQ API. They contain values via data, and are a of a certain type.
282
+ class CustomSourceOrTargetField(BaseModel):
283
+ """Custom field type from external system.
284
+
285
+ Attributes:
286
+ type: Always "CUSTOM" for this field type
287
+ data: List of custom field descriptors
288
+ """
289
+ type: Literal["CUSTOM"] = "CUSTOM"
290
+ data: List[CustomDataValues]
291
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
292
+
293
+
294
+ class LibrarySourceOrTargetField(BaseModel):
295
+ """Library field type from BrynQ library.
296
+
297
+ Attributes:
298
+ type: Always "LIBRARY" for this field type
299
+ data: List of library field identifiers or metadata objects
300
+ """
301
+ type: Literal["LIBRARY"] = "LIBRARY"
302
+ data: List[Union[str, LibraryFieldValues]]
303
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
304
+
305
+
306
+ class EmptySourceOrTargetField(BaseModel):
307
+ """Empty field type (no value).
308
+
309
+ Attributes:
310
+ type: Always "EMPTY" for this field type
311
+ data: Empty list (no data for empty fields)
312
+ """
313
+ type: Literal['EMPTY'] = "EMPTY"
314
+ data: List[Any] = Field(default_factory=list)
315
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
316
+
317
+
318
+ class FixedSourceOrTargetField(BaseModel):
319
+ """Fixed/literal value field type.
320
+
321
+ Attributes:
322
+ type: Always "FIXED" for this field type
323
+ data: The fixed literal value string
324
+ """
325
+ type: Literal["FIXED"] = "FIXED"
326
+ data: str
327
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
328
+
329
+
330
+ class ConfigurationSourceOrTargetField(BaseModel):
331
+ """Configuration field type.
332
+
333
+ Attributes:
334
+ type: Always "CONFIGURATION" for this field type
335
+ data: List of configuration field values
336
+ """
337
+ type: Literal["CONFIGURATION"] = "CONFIGURATION"
338
+ data: List[ConfigFieldValues]
339
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True, extra="allow")
340
+
341
+ # General field class that can be any of the above types, distinguished by the 'type' value.
342
+ SourceOrTargetField = Annotated[
343
+ Union[CustomSourceOrTargetField, LibrarySourceOrTargetField, FixedSourceOrTargetField, EmptySourceOrTargetField, ConfigurationSourceOrTargetField],
344
+ Field(discriminator="type")
345
+ ]
346
+
347
+ # ============================================================================
348
+ # 2. Scenario Structure API Models
349
+ # ============================================================================
350
+ class ScenarioMappingConfiguration(BaseModel):
351
+ """Value mapping configuration for translating source values to target values.
352
+
353
+ Attributes:
354
+ values: Explicit mapping values when value mapping is required
355
+ default_value: Fallback value applied when no mapping match is found (aliased as "defaultValue" in API)
356
+ """
357
+ values: List[MappingValue] = Field(default_factory=list)
358
+ default_value: str = Field(default="", alias="defaultValue")
359
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
360
+
361
+
362
+ class ScenarioDetail(BaseModel):
363
+ """Represents a single detail/mapping within a scenario. Called a 'record' when parsed.
364
+
365
+ Represents the content fields of a scenario detail mapping:
366
+ - id: Primary key of the detail record
367
+ - logic: Optional transformation logic
368
+ - unique: Whether this mapping must be unique across the scenario
369
+ - required: Whether the field is mandatory
370
+ - mapping_required: Indicates if an explicit mapping table is required (aliased as "mappingRequired" in API)
371
+ - source: Source field definition
372
+ - target: Target field definition
373
+ - mapping: Mapping or value-translation configuration (may be absent)
374
+ """
375
+ id: str = Field(..., description="Primary key of the detail record")
376
+ logic: str = Field(default="", description="Optional transformation logic")
377
+ unique: bool = Field(default=False, description="Must this mapping be unique across the scenario?")
378
+ required: bool = Field(default=False, description="Is the field mandatory?")
379
+ mapping_required: Optional[bool] = Field(default=False, alias="mappingRequired", description="Flag indicating whether an explicit mapping table is needed")
380
+ source: SourceOrTargetField = Field(..., description="One or more source fields, each of type 'CUSTOM', 'LIBRARY', 'FIXED', 'EMPTY', or 'CONFIGURATION'.")
381
+ target: SourceOrTargetField = Field(..., description="One or more target fields, each of type 'CUSTOM', 'LIBRARY', 'FIXED', or 'CONFIGURATION'.")
382
+ mapping: Optional[ScenarioMappingConfiguration] = Field(default=None, description="Mapping/value-translation configuration (may be absent)")
383
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
384
+
385
+
386
+ # ============================================================================
387
+ # 3. Scenario API Model
388
+ # ============================================================================
389
+ class Scenario(BaseModel):
390
+ """Raw scenario model as returned by the API.
391
+
392
+ Attributes:
393
+ id: Scenario identifier
394
+ name: Scenario display name
395
+ description: Scenario business context
396
+ details: Collection of field-level mappings
397
+ """
398
+ id: str = Field(..., description="Scenario identifier")
399
+ name: str = Field(..., description="Scenario display name")
400
+ description: str = Field(default="", description="Scenario business context")
401
+ details: List[ScenarioDetail] = Field(..., description="Collection of field-level mappings")
402
+
403
+ class Config:
404
+ frozen = True
405
+ strict = True
406
+ populate_by_name = True
407
+
408
+ # ============================================================================
409
+ # 4. Template API Model
410
+ # ============================================================================
411
+ class Template(RootModel[List[Scenario]]):
412
+ """Template model representing a list of scenarios.
413
+
414
+ In JSON format, this is represented as an array of scenario objects:
415
+ [{scenario}, {scenario}, ...]
416
+
417
+ The root value is a list of Scenario objects.
418
+ """
419
+ model_config = ConfigDict(frozen=True, strict=True)
@@ -0,0 +1,126 @@
1
+ from typing import List, Optional, Dict
2
+ from pydantic import BaseModel, Field, validator
3
+
4
+ class UserProducts(BaseModel):
5
+ """Schema for user product settings"""
6
+ qlik_sense_analyzer: bool = Field(..., alias="qlikSenseAnalyzer", description="Whether user has Qlik Sense Analyzer access")
7
+ qlik_sense_professional: bool = Field(..., alias="qlikSenseProfessional", description="Whether user has Qlik Sense Professional access")
8
+
9
+ class Config:
10
+ frozen = True
11
+ strict = True
12
+ populate_by_name = True
13
+
14
+ class UserCreate(BaseModel):
15
+ """Schema for creating a user"""
16
+ name: str = Field(..., description="User name")
17
+ username: str = Field(..., description="Username")
18
+ email: str = Field(..., description="User email")
19
+ language: str = Field(..., description="User language preference")
20
+ products: UserProducts = Field(..., description="User product access settings")
21
+
22
+ class Config:
23
+ frozen = True
24
+ strict = True
25
+ populate_by_name = True
26
+
27
+ class UserUpdate(BaseModel):
28
+ """Schema for updating a user"""
29
+ name: Optional[str] = Field(None, description="User name")
30
+ username: Optional[str] = Field(None, description="Username")
31
+ email: Optional[str] = Field(None, description="User email")
32
+ roles: Optional[List[int]] = Field(None, description="List of role IDs to assign")
33
+ language: Optional[str] = Field(None, description="User language preference")
34
+ products: Optional[dict] = Field(None, description="User product access settings")
35
+
36
+ class Config:
37
+ frozen = True
38
+ strict = True
39
+ populate_by_name = True
40
+
41
+ class UserInvite(BaseModel):
42
+ """Schema for inviting a user"""
43
+ email: str = Field(..., description="User email")
44
+ products: Optional[dict] = Field(None, description="User product access settings")
45
+
46
+ class Config:
47
+ frozen = True
48
+ strict = True
49
+ populate_by_name = True
50
+
51
+ class QlikDashboardRight(BaseModel):
52
+ """Schema for Qlik dashboard right"""
53
+ guid: str = Field(..., description="Dashboard GUID")
54
+ data_model_edit: bool = Field(..., alias="dataModelEdit", description="Whether data model is editable")
55
+ editable: bool = Field(..., description="Whether dashboard is editable")
56
+ organigrams: List[int] = Field(default_factory=list, description="List of organigram IDs")
57
+
58
+ class Config:
59
+ frozen = True
60
+ strict = True
61
+ populate_by_name = True
62
+
63
+ class QlikDashboardRightsPayload(BaseModel):
64
+ """Schema for Qlik dashboard rights payload"""
65
+ dashboard_rights: List[QlikDashboardRight] = Field(..., alias="dashboardRights", description="List of dashboard rights")
66
+
67
+ class Config:
68
+ frozen = True
69
+ strict = True
70
+ populate_by_name = True
71
+
72
+ class DashboardRight(BaseModel):
73
+ """Schema for dashboard right"""
74
+ dashboard_id: int = Field(..., alias="dashboardId", description="Dashboard ID")
75
+ editable: bool = Field(..., description="Whether dashboard is editable")
76
+ organigrams: List[int] = Field(default_factory=list, description="List of organigram IDs")
77
+
78
+ class Config:
79
+ frozen = True
80
+ strict = True
81
+ populate_by_name = True
82
+
83
+ class DashboardRightsPayload(BaseModel):
84
+ """Schema for dashboard rights payload"""
85
+ dashboard_rights: List[DashboardRight] = Field(..., alias="dashboardRights", description="List of dashboard rights")
86
+
87
+ class Config:
88
+ frozen = True
89
+ strict = True
90
+ populate_by_name = True
91
+
92
+
93
+ class UserEntitiesPayload(BaseModel):
94
+ """Schema for user entities payload"""
95
+ entities: List[int] = Field(..., description="List of entity IDs to assign")
96
+
97
+ class Config:
98
+ frozen = True
99
+ strict = True
100
+ populate_by_name = True
101
+
102
+ class User(BaseModel):
103
+ """Schema for user data"""
104
+ id: int = Field(..., description="User ID")
105
+ name: str = Field(..., description="User name")
106
+ email: str = Field(..., description="User email")
107
+
108
+ roles: List[int] = Field(default_factory=list, description="User roles")
109
+ organization_chart_entities: List[int] = Field(default_factory=list, description="Organization chart entities")
110
+ qlik_dashboards: List[dict] = Field(default_factory=list, description="Qlik dashboards")
111
+ dashboards: List[dict] = Field(default_factory=list, description="Standard dashboards")
112
+
113
+ class Config:
114
+ frozen = True
115
+ strict = True
116
+ populate_by_name = True
117
+
118
+ class QlikAppUserAuthorization(BaseModel):
119
+ """Schema for Qlik app user authorization"""
120
+ username: Optional[str] = None
121
+ user_id: int = Field(alias="userId")
122
+ entity_codes: List[str] = Field(default_factory=list, alias="entityCodes")
123
+
124
+ class Config:
125
+ frozen = True
126
+ populate_by_name = True