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,52 @@
1
+ from .credentials import CredentialsConfig, CredentialSource, CredentialData
2
+ from .customers import CustomerSchema, CustomerUsers, CustomerContractDetailsSchema
3
+ from .scenarios import Scenario, ScenarioMappingConfiguration, ScenarioDetail, SourceOrTargetField
4
+ from .interfaces import Interface, InterfaceApps, InterfaceDetail, InterfaceConfig, Schedule, Scope, DevSettings, Frequency, TaskSchedule, MappingValue, MappingItem
5
+ from .organization_chart import OrganizationChartNode, OrganizationLayerCreate, OrganizationLayerUpdate, OrganizationLayerGet, OrganizationNode, OrganizationNodeCreate, OrganizationNodeUpdate
6
+ from .roles import DashboardRight, QlikDashboardRight, CreateRoleRequest, RoleUser, RoleSchema
7
+ from .users import UserProducts, UserCreate, UserUpdate, UserInvite, QlikDashboardRight, QlikDashboardRightsPayload, DashboardRight, DashboardRightsPayload, UserEntitiesPayload, User, QlikAppUserAuthorization
8
+ from .interfaces import MappingValue
9
+
10
+ __all__ = [
11
+ "CredentialsConfig",
12
+ "CredentialSource",
13
+ "CredentialData",
14
+ "CustomerSchema",
15
+ "CustomerUsers",
16
+ "CustomerContractDetailsSchema",
17
+ "Interface",
18
+ "InterfaceApps",
19
+ "InterfaceDetail",
20
+ "InterfaceConfig",
21
+ "Schedule",
22
+ "Scope",
23
+ "DevSettings",
24
+ "Frequency",
25
+ "TaskSchedule",
26
+ "MappingValue",
27
+ "MappingItem",
28
+ "OrganizationChartNode",
29
+ "OrganizationLayerCreate",
30
+ "OrganizationLayerUpdate",
31
+ "OrganizationLayerGet",
32
+ "OrganizationNode",
33
+ "OrganizationNodeCreate",
34
+ "OrganizationNodeUpdate",
35
+ "DashboardRight",
36
+ "QlikDashboardRight",
37
+ "CreateRoleRequest",
38
+ "RoleUser",
39
+ "UserProducts",
40
+ "UserCreate",
41
+ "UserUpdate",
42
+ "UserInvite",
43
+ "QlikDashboardRight",
44
+ "QlikDashboardRightsPayload",
45
+ "DashboardRight",
46
+ "DashboardRightsPayload",
47
+ "UserEntitiesPayload",
48
+ "User",
49
+ "QlikAppUserAuthorization",
50
+ "MappingValue",
51
+ "Scenario",
52
+ ]
@@ -0,0 +1,37 @@
1
+ from typing import Dict, Any, List
2
+ from pydantic import BaseModel, Field, RootModel
3
+
4
+
5
+ class CredentialData(RootModel[Dict[str, Any]]):
6
+ """Schema for credential data which can contain any key-value pairs"""
7
+ root: Dict[str, Any]
8
+
9
+ class Config:
10
+ frozen = True
11
+ strict = False
12
+
13
+
14
+ class CredentialSource(BaseModel):
15
+ """Schema for a credential source or target"""
16
+ app: str = Field(..., description="Application identifier")
17
+ type: str = Field(..., description="Type of the credential source/target")
18
+ data: Dict[str, Any] = Field(..., description="Credential data key-value pairs")
19
+ direction: str = Field(..., description="Direction of the credential flow (source/target)")
20
+
21
+ class Config:
22
+ frozen = True
23
+ strict = False
24
+ populate_by_name = True
25
+ extra = 'allow'
26
+
27
+
28
+ class CredentialsConfig(BaseModel):
29
+ """Schema for the complete credentials configuration"""
30
+ sources: List[CredentialSource] = Field(..., description="List of credential sources")
31
+ targets: List[CredentialSource] = Field(..., description="List of credential targets")
32
+
33
+ class Config:
34
+ frozen = True
35
+ strict = False
36
+ populate_by_name = True
37
+ extra = 'allow'
@@ -0,0 +1,108 @@
1
+ from pydantic import BaseModel, Field, ValidationError
2
+ from typing import Optional, Union, List, Dict, Any, Tuple, Type
3
+ from datetime import date
4
+ from pydantic.functional_validators import BeforeValidator
5
+ from typing_extensions import Annotated
6
+
7
+
8
+ def parse_date(value: Union[str, None]) -> Optional[date]:
9
+ """Convert string date to date object or return None"""
10
+ if value is None:
11
+ return None
12
+ if isinstance(value, date):
13
+ return value
14
+ return date.fromisoformat(value)
15
+
16
+
17
+ def convert_to_str(value: Union[str, int]) -> str:
18
+ """Convert integer or string value to string"""
19
+ return str(value)
20
+
21
+
22
+ DateField = Annotated[Union[date, None], BeforeValidator(parse_date)]
23
+ StringOrInt = Annotated[str, BeforeValidator(convert_to_str)]
24
+
25
+
26
+ def validate_data(data: List[Dict[str, Any]], schema: Type[BaseModel], debug: bool = False) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
27
+ """Validate a list of dictionaries using a Pydantic schema and separate valid and invalid data.
28
+
29
+ Args:
30
+ data (List[Dict[str, Any]]): List of dictionaries to validate
31
+ schema (Type[BaseModel]): Pydantic schema class to use for validation
32
+ debug (bool, optional): Whether to print debug information. Defaults to False.
33
+
34
+ Returns:
35
+ Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: Tuple of (valid_data, invalid_data)
36
+ - valid_data: List of validated dictionaries that conform to the schema
37
+ - invalid_data: List of dictionaries that failed validation, with error details
38
+ """
39
+ valid_data = []
40
+ invalid_data = []
41
+
42
+ for idx, item in enumerate(data):
43
+ try:
44
+ validated = schema(**item).model_dump()
45
+ valid_data.append(validated)
46
+ except ValidationError as e:
47
+ if debug:
48
+ print(f"Validation error at index {idx}:")
49
+ print(e.json(indent=2))
50
+
51
+ # Add error information to the invalid item
52
+ invalid_item = item.copy()
53
+ invalid_item['_validation_errors'] = [
54
+ {
55
+ 'loc': ' -> '.join(str(loc) for loc in error['loc']),
56
+ 'msg': error['msg'],
57
+ 'type': error['type']
58
+ }
59
+ for error in e.errors()
60
+ ]
61
+ invalid_data.append(invalid_item)
62
+
63
+ return valid_data, invalid_data
64
+
65
+
66
+ class CustomerSchema(BaseModel):
67
+ """Schema for basic customer data validation"""
68
+ name: str = Field(..., description="Name of the customer")
69
+ domain: str = Field(..., description="Domain identifier for the customer")
70
+
71
+ class Config:
72
+ frozen = True
73
+ strict = True
74
+ populate_by_name = True
75
+
76
+
77
+ class CustomerUsers(BaseModel):
78
+ """Schema for customer users counts"""
79
+ global_: StringOrInt = Field(..., alias="global", description="Number of global users")
80
+ qlik_analyzer: StringOrInt = Field(..., alias="qlikAnalyzer", description="Number of Qlik Analyzer users")
81
+ qlik_pro: StringOrInt = Field(..., alias="qlikPro", description="Number of Qlik Pro users")
82
+ jira: int = Field(..., description="Number of Jira users")
83
+
84
+ class Config:
85
+ frozen = True
86
+ strict = True
87
+ populate_by_name = True
88
+
89
+
90
+ class CustomerContractDetailsSchema(BaseModel):
91
+ """Schema for customer contract details"""
92
+ id: int = Field(..., description="Unique identifier for the customer", gt=0)
93
+ name: str = Field(..., description="Name of the customer")
94
+ profit_subscription: Optional[str] = Field(None, alias="profitSubscription", description="Profit subscription identifier")
95
+ subscription_cost: float = Field(..., alias="subscriptionCost", description="Cost of the subscription")
96
+ subscription_cost_mutation: Optional[float] = Field(None, alias="subscriptionCostMutation", description="Change in subscription cost")
97
+ effective_date: DateField = Field(..., alias="effectiveDate", description="Start date of the contract")
98
+ expiration_date: DateField = Field(None, alias="expirationDate", description="End date of the contract")
99
+ payment_term: str = Field(..., alias="paymentTerm", description="Payment term (e.g., annual)")
100
+ subscription_type: str = Field(..., alias="subscriptionType", description="Type of subscription")
101
+ referred_by: Optional[str] = Field(None, alias="referredBy", description="Referral source")
102
+ users: CustomerUsers = Field(..., description="User counts by type")
103
+ apps: int = Field(..., description="Number of apps")
104
+
105
+ class Config:
106
+ frozen = True
107
+ strict = True
108
+ populate_by_name = True
@@ -0,0 +1,237 @@
1
+ from typing import Dict, List, Any, Optional, Union, Annotated
2
+ from datetime import datetime
3
+ from enum import Enum
4
+ import re
5
+ from pydantic import BaseModel, Field, field_validator, model_validator, ConfigDict, StringConstraints
6
+
7
+ SDK_PREFIX = "brynq_sdk_"
8
+ VERSION_PATTERN = r'^\d+\.\d+\.\d+$'
9
+
10
+ # strips whitespace from strings before validation
11
+ CleanStr = Annotated[str, StringConstraints(strip_whitespace=True)]
12
+
13
+
14
+ class InterfaceType(str, Enum):
15
+ """Enumeration of interface types."""
16
+ TEMPLATE = "TEMPLATE"
17
+ ADVANCED = "ADVANCED"
18
+
19
+ class Frequency(BaseModel):
20
+ """Schema for task schedule frequency"""
21
+ day: int = Field(..., description="Day of frequency")
22
+ hour: int = Field(..., description="Hour of frequency")
23
+ month: int = Field(..., description="Month of frequency")
24
+ minute: int = Field(..., description="Minute of frequency")
25
+
26
+ class Config:
27
+ frozen = True
28
+ strict = True
29
+ populate_by_name = True
30
+
31
+
32
+ class TaskSchedule(BaseModel):
33
+ """Schema for interface task schedule"""
34
+ id: int = Field(..., description="Task schedule ID")
35
+ task_type: str = Field(..., alias="taskType", description="Type of task")
36
+ trigger_type: str = Field(..., alias="triggerType", description="Type of trigger")
37
+ trigger_pattern: str = Field(..., alias="triggerPattern", description="Pattern of trigger")
38
+ timezone: str = Field(..., description="Timezone for the task")
39
+ next_reload: Optional[str] = Field(None, alias="nextReload", description="Next reload time")
40
+ frequency: Frequency = Field(..., description="Frequency settings")
41
+ variables: Dict[str, Any] = Field(default_factory=dict, description="Task variables")
42
+ start_after_preceding_task: Optional[bool] = Field(None, alias="startAfterPrecedingTask", description="Whether to start after preceding task")
43
+ start_after_task_id: Optional[int] = Field(None, alias="startAfterTaskId", description="ID of task to start after")
44
+ last_reload: str = Field(..., alias="lastReload", description="Last reload time")
45
+ last_error_message: str = Field(..., alias="lastErrorMessage", description="Last error message")
46
+ status: str = Field(..., description="Current status")
47
+ disabled: bool = Field(..., description="Whether task is disabled")
48
+ run_instant: bool = Field(..., alias="runInstant", description="Whether to run instantly")
49
+ stopped_by_user: bool = Field(..., alias="stoppedByUser", description="Whether stopped by user")
50
+ stepnr: int = Field(..., description="Step number")
51
+ created_at: str = Field(..., alias="createdAt", description="Creation time")
52
+ updated_at: str = Field(..., alias="updatedAt", description="Last update time")
53
+
54
+ @field_validator('last_reload', 'created_at', 'updated_at', 'next_reload')
55
+ def validate_datetime(cls, v: Optional[str]) -> Optional[str]:
56
+ """Validate that the string is a valid ISO format datetime"""
57
+ if v is None:
58
+ return v
59
+ try:
60
+ datetime.fromisoformat(v.replace('Z', '+00:00'))
61
+ return v
62
+ except (ValueError, AttributeError) as e:
63
+ raise ValueError(f"Invalid datetime format: {str(e)}")
64
+
65
+ class Config:
66
+ frozen = True
67
+ strict = True
68
+ populate_by_name = True
69
+
70
+
71
+ class Interface(BaseModel):
72
+ """Schema for interface data"""
73
+ id: int = Field(..., description="Interface ID")
74
+ name: str = Field(..., description="Interface name")
75
+ description: str = Field(..., description="Interface description")
76
+ source_systems: List[int] = Field(..., alias="sourceSystems", description="List of source system IDs")
77
+ target_systems: List[int] = Field(..., alias="targetSystems", description="List of target system IDs")
78
+ task_schedule: TaskSchedule = Field(..., alias="taskSchedule", description="Task schedule details")
79
+
80
+ class Config:
81
+ frozen = True
82
+ strict = True
83
+ populate_by_name = True
84
+
85
+
86
+ class InterfaceAppConfig(BaseModel):
87
+ """Schema for individual app configuration within interface apps.
88
+
89
+ Attributes:
90
+ app: Application name
91
+ sdk: SDK package name (must start with "brynq_sdk_")
92
+ sdk_version: SDK version in format digits.digits.digits (aliased as "sdkVersion" in API)
93
+ """
94
+ model_config = ConfigDict(frozen=True, strict=True, populate_by_name=True)
95
+
96
+ app: CleanStr = Field(..., description="Application name")
97
+ sdk: CleanStr = Field(default="", description="SDK package name (may be empty for ADVANCED interfaces)")
98
+ sdk_version: CleanStr = Field(default="", alias="sdkVersion", description="SDK version (may be empty for ADVANCED interfaces)")
99
+
100
+ @field_validator('sdk')
101
+ @classmethod
102
+ def validate_sdk_prefix(cls, v: str) -> str:
103
+ """Validate that SDK name starts with 'brynq_sdk_' or is empty."""
104
+ if v and not v.startswith(SDK_PREFIX):
105
+ raise ValueError(f"SDK name must start with '{SDK_PREFIX}' or be empty, got: {v}")
106
+ return v
107
+
108
+ @field_validator('sdk_version')
109
+ @classmethod
110
+ def validate_version_format(cls, v: str) -> str:
111
+ """Validate that version follows digits.digits.digits format or is empty."""
112
+ if v and not re.match(VERSION_PATTERN, v):
113
+ raise ValueError(f"SDK version must be 'digits.digits.digits' (e.g., '2.8.2'), got: {v}")
114
+ return v
115
+
116
+
117
+ class InterfaceApps(BaseModel):
118
+ """Schema for interface apps configuration"""
119
+ source: InterfaceAppConfig = Field(..., description="Source application configuration")
120
+ target: InterfaceAppConfig = Field(..., description="Target application configuration")
121
+
122
+ class Config:
123
+ frozen = True
124
+ strict = True
125
+ populate_by_name = True
126
+
127
+
128
+ class InterfaceDetail(BaseModel):
129
+ """Schema for detailed interface information"""
130
+ model_config = ConfigDict(frozen=True, strict=False, populate_by_name=True)
131
+
132
+ id: int = Field(..., description="Interface id")
133
+ uuid: str = Field(..., description="Interace uid")
134
+ name: str = Field(..., description="Interface name")
135
+ type: InterfaceType = Field(..., description="Interface type (TEMPLATE or ADVANCED)")
136
+ in_development: bool = Field(..., description="Whether interface is in development.", alias="inDevelopment")
137
+ apps: InterfaceApps = Field(..., description="Interface applications configuration")
138
+
139
+ @model_validator(mode='after')
140
+ def validate_apps_for_type(self) -> 'InterfaceDetail':
141
+ """Validate that sdk and sdk_version are non-empty unless type is ADVANCED."""
142
+ if self.type == InterfaceType.TEMPLATE:
143
+ for app_name in ['source', 'target']:
144
+ config = getattr(self.apps, app_name)
145
+ if not config.sdk:
146
+ raise ValueError(f"{app_name}.sdk must be non-empty for interface type {self.type.value}")
147
+ if not config.sdk_version:
148
+ raise ValueError(f"{app_name}.sdk_version must be non-empty for interface type {self.type.value}")
149
+ return self
150
+
151
+
152
+ class MappingValue(BaseModel):
153
+ """Schema for a single mapping value"""
154
+ input: Dict[Any, Any] = Field(..., description="Input mapping key-value pairs")
155
+ output: Dict[Any, Any] = Field(..., description="Output mapping key-value pairs")
156
+
157
+ class Config:
158
+ frozen = True
159
+ strict = True
160
+ populate_by_name = True
161
+
162
+
163
+ class MappingItem(BaseModel):
164
+ """Schema for a single mapping configuration"""
165
+ guid: str = Field(..., description="Unique identifier for the mapping")
166
+ name: str = Field(..., description="Name of the mapping")
167
+ values: List[MappingValue] = Field(default_factory=list, description="List of mapping values")
168
+ default_value: Optional[str] = Field(None, alias="defaultValue", description="Default value if no mapping matches")
169
+
170
+ class Config:
171
+ frozen = True
172
+ strict = True
173
+ populate_by_name = True
174
+
175
+
176
+ class InterfaceConfig(BaseModel):
177
+ """Schema for interface configuration"""
178
+ mapping: List[Dict[str, Any]] = Field(default_factory=list, description="List of mappings")
179
+ variables: Dict[str, Any] = Field(default_factory=dict, description="Configuration variables")
180
+
181
+ class Config:
182
+ frozen = True
183
+ strict = True
184
+ populate_by_name = True
185
+
186
+
187
+ class Schedule(BaseModel):
188
+ """Schema for interface schedule configuration"""
189
+ id: int = Field(..., description="Schedule ID")
190
+ trigger_type: str = Field(..., alias="triggerType", description="Type of trigger")
191
+ trigger_pattern: str = Field(..., alias="triggerPattern", description="Pattern for the trigger")
192
+ timezone: str = Field(..., description="Timezone setting")
193
+ next_reload: Optional[str] = Field(None, alias="nextReload", description="Next scheduled reload time")
194
+ frequency: Frequency = Field(..., description="Frequency settings")
195
+ start_after_preceding_task: Optional[bool] = Field(None, alias="startAfterPrecedingTask", description="Whether to start after preceding task")
196
+ last_reload: str = Field(..., alias="lastReload", description="Last reload time")
197
+ last_error_message: str = Field(..., alias="lastErrorMessage", description="Last error message")
198
+
199
+ @field_validator('last_reload', 'next_reload')
200
+ def validate_datetime(cls, v: Optional[str]) -> Optional[str]:
201
+ """Validate that the string is a valid ISO format datetime"""
202
+ if v is None:
203
+ return v
204
+ try:
205
+ datetime.fromisoformat(v.replace('Z', '+00:00'))
206
+ return v
207
+ except (ValueError, AttributeError) as e:
208
+ raise ValueError(f"Invalid datetime format: {str(e)}")
209
+
210
+ class Config:
211
+ frozen = True
212
+ strict = True
213
+ populate_by_name = True
214
+
215
+
216
+ class Scope(BaseModel):
217
+ """Schema for interface scope data"""
218
+ live: Optional[Dict[str, Any]] = Field(None, description="Live scope configuration")
219
+ draft: Optional[Dict[str, Any]] = Field(None, description="Draft scope configuration")
220
+
221
+ class Config:
222
+ frozen = True
223
+ strict = True
224
+ populate_by_name = True
225
+
226
+
227
+ class DevSettings(BaseModel):
228
+ """Schema for interface dev settings"""
229
+ docker_image: str = Field(..., alias="dockerImage", description="Docker image name")
230
+ sftp_mapping: List[dict] = Field(..., alias="sftpMapping", description="SFTP mapping configuration")
231
+ runfile_path: str = Field(..., alias="runfilePath", description="Path to the runfile")
232
+ stop_is_allowed: bool = Field(..., alias="stopIsAllowed", description="Whether stopping is allowed")
233
+
234
+ class Config:
235
+ frozen = True
236
+ strict = True
237
+ populate_by_name = True
@@ -0,0 +1,70 @@
1
+ from typing import List, Optional
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class OrganizationChartNode(BaseModel):
6
+ """Schema for organization chart node"""
7
+ id: int = Field(..., description="Node ID")
8
+ name: str = Field(..., description="Node name")
9
+ drop_index: int = Field(..., alias="dropIndex", description="Drop index for ordering")
10
+ parent_id: Optional[int] = Field(None, alias="parent_id", description="Parent node ID, null for root nodes")
11
+ source_system_entities: List[str] = Field(default_factory=list, alias="source_system_entities", description="List of source system entities")
12
+
13
+ class Config:
14
+ frozen = True
15
+ strict = True
16
+ populate_by_name = True
17
+
18
+
19
+ class OrganizationLayerCreate(BaseModel):
20
+ """Schema for organization layer input"""
21
+ name: str = Field(..., description="Layer name")
22
+ level: int = Field(..., ge=0, description="Layer level in hierarchy")
23
+
24
+ class Config:
25
+ frozen = True
26
+ strict = True
27
+ populate_by_name = True
28
+
29
+
30
+ class OrganizationLayerUpdate(BaseModel):
31
+ """Schema for organization layer response"""
32
+ id: int = Field(..., description="Layer ID")
33
+ level: int = Field(None, ge=0, description="Layer level in hierarchy")
34
+ name: str = Field(None, description="Layer name")
35
+
36
+ class Config:
37
+ frozen = True
38
+ strict = True
39
+ populate_by_name = True
40
+
41
+
42
+ class OrganizationLayerGet(OrganizationLayerUpdate):
43
+ pass
44
+
45
+
46
+ class OrganizationNode(BaseModel):
47
+ """Schema for organization chart node"""
48
+ id: int = Field(..., description="Node ID")
49
+ name: str = Field(..., description="Node name")
50
+ parent_id: Optional[int] = Field(None, alias="parentId", description="Parent node ID")
51
+
52
+ class Config:
53
+ frozen = True
54
+ strict = True
55
+ populate_by_name = True
56
+
57
+
58
+ class OrganizationNodeCreate(BaseModel):
59
+ """Schema for creating organization chart node"""
60
+ name: str = Field(..., description="Node name")
61
+ parent_id: Optional[int] = Field(None, alias="parentId", description="Parent node ID")
62
+ position: Optional[int] = Field(None, description="Position among siblings")
63
+
64
+ class Config:
65
+ frozen = True
66
+ strict = True
67
+ populate_by_name = True
68
+
69
+ class OrganizationNodeUpdate(OrganizationNodeCreate):
70
+ id: int = Field(..., description="Node ID")
@@ -0,0 +1,95 @@
1
+ from pydantic import BaseModel, Field, field_validator
2
+ from typing import List, Optional, Any, Dict
3
+
4
+ class DashboardRight(BaseModel):
5
+ """Schema for dashboard rights"""
6
+ dashboard_id: int = Field(..., alias="dashboardId", description="ID of the dashboard")
7
+ editable: bool = Field(..., description="Whether the dashboard is editable")
8
+ entities: List[int] = Field(default_factory=list, description="List of entity IDs")
9
+
10
+ class Config:
11
+ frozen = True
12
+ strict = True
13
+ populate_by_name = True
14
+
15
+
16
+ class QlikDashboardRight(BaseModel):
17
+ """Schema for Qlik dashboard rights"""
18
+ guid: str = Field(..., description="Dashboard GUID")
19
+ data_model_editable: bool = Field(..., alias="dataModelEditable", description="Whether the data model is editable")
20
+ editable: bool = Field(..., description="Whether the dashboard is editable")
21
+ entities: List[int] = Field(default_factory=list, description="List of entity IDs")
22
+
23
+ class Config:
24
+ frozen = True
25
+ strict = True
26
+ populate_by_name = True
27
+
28
+
29
+ class CreateRoleRequest(BaseModel):
30
+ """Schema for creating a new role"""
31
+ name: str = Field(..., description="Name of the role")
32
+ dashboards: List[DashboardRight] = Field(default_factory=list, description="List of dashboard rights")
33
+ qlik_dashboards: List[QlikDashboardRight] = Field(default_factory=list, alias="qlikDashboards", description="List of Qlik dashboard rights")
34
+
35
+ class Config:
36
+ frozen = True
37
+ strict = True
38
+ populate_by_name = True
39
+
40
+
41
+ class RoleUser(BaseModel):
42
+ """Schema for users assigned to a role"""
43
+ id: int = Field(..., description="User ID")
44
+ name: str = Field(..., description="User name")
45
+ email: str = Field(..., description="User email")
46
+ active: bool = Field(..., description="Whether the user is active")
47
+
48
+ class Config:
49
+ frozen = True
50
+ strict = True
51
+ populate_by_name = True
52
+
53
+
54
+ class RoleSchema(BaseModel):
55
+ """Schema for role data"""
56
+ id: int = Field(..., description="Unique identifier for the role")
57
+ name: str = Field(..., description="Name of the role")
58
+ permissions: Optional[Dict[str, Any]] = Field(None, description="Role permissions")
59
+ dashboards: List[Dict[str, Any]] = Field(default_factory=list, description="List of dashboard rights")
60
+ qlik_dashboards: List[Dict[str, Any]] = Field(default_factory=list, alias="qlikDashboards", description="List of Qlik dashboard rights")
61
+
62
+ class Config:
63
+ frozen = True
64
+ strict = True
65
+ populate_by_name = True
66
+
67
+
68
+ class RoleSchema(BaseModel):
69
+ """Schema for role data validation"""
70
+ id: int = Field(..., description="Unique identifier for the role", gt=0)
71
+ name: str = Field(None, description="Name of the role")
72
+ dashboards: List[Any] = Field(default_factory=list, description="List of dashboards associated with the role")
73
+ permissions: Optional[Any] = Field(default=None, description="Role permissions")
74
+ qlik_dashboards: List[Any] = Field(default_factory=list, description="List of Qlik dashboards associated with the role")
75
+
76
+ @field_validator('name')
77
+ @classmethod
78
+ def validate_name(cls, v: str) -> str:
79
+ """Validate that the name is not empty or just whitespace"""
80
+ if not v.strip():
81
+ raise ValueError("Role name cannot be empty or just whitespace")
82
+ return v.strip()
83
+
84
+ @field_validator('dashboards', 'qlik_dashboards')
85
+ @classmethod
86
+ def validate_dashboard_lists(cls, v: List[Any]) -> List[Any]:
87
+ """Ensure dashboard lists are always lists, even if empty"""
88
+ if v is None:
89
+ return []
90
+ return v
91
+
92
+ class Config:
93
+ frozen = True # Makes the model immutable
94
+ strict = True # Ensures strict type checking
95
+ populate_by_name = True # Allows both camelCase and snake_case access