nexo-schemas 0.0.16__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.
Files changed (69) hide show
  1. nexo/schemas/__init__.py +0 -0
  2. nexo/schemas/application.py +292 -0
  3. nexo/schemas/connection.py +134 -0
  4. nexo/schemas/data.py +27 -0
  5. nexo/schemas/document.py +237 -0
  6. nexo/schemas/error/__init__.py +476 -0
  7. nexo/schemas/error/constants.py +50 -0
  8. nexo/schemas/error/descriptor.py +354 -0
  9. nexo/schemas/error/enums.py +40 -0
  10. nexo/schemas/error/metadata.py +15 -0
  11. nexo/schemas/error/spec.py +312 -0
  12. nexo/schemas/exception/__init__.py +0 -0
  13. nexo/schemas/exception/exc.py +911 -0
  14. nexo/schemas/exception/factory.py +1928 -0
  15. nexo/schemas/exception/handlers.py +110 -0
  16. nexo/schemas/google.py +14 -0
  17. nexo/schemas/key/__init__.py +0 -0
  18. nexo/schemas/key/rsa.py +131 -0
  19. nexo/schemas/metadata.py +21 -0
  20. nexo/schemas/mixins/__init__.py +0 -0
  21. nexo/schemas/mixins/filter.py +140 -0
  22. nexo/schemas/mixins/general.py +65 -0
  23. nexo/schemas/mixins/hierarchy.py +19 -0
  24. nexo/schemas/mixins/identity.py +387 -0
  25. nexo/schemas/mixins/parameter.py +50 -0
  26. nexo/schemas/mixins/service.py +40 -0
  27. nexo/schemas/mixins/sort.py +111 -0
  28. nexo/schemas/mixins/timestamp.py +192 -0
  29. nexo/schemas/model.py +240 -0
  30. nexo/schemas/operation/__init__.py +0 -0
  31. nexo/schemas/operation/action/__init__.py +9 -0
  32. nexo/schemas/operation/action/base.py +14 -0
  33. nexo/schemas/operation/action/resource.py +371 -0
  34. nexo/schemas/operation/action/status.py +8 -0
  35. nexo/schemas/operation/action/system.py +6 -0
  36. nexo/schemas/operation/action/websocket.py +6 -0
  37. nexo/schemas/operation/base.py +289 -0
  38. nexo/schemas/operation/constants.py +18 -0
  39. nexo/schemas/operation/context.py +68 -0
  40. nexo/schemas/operation/dependency.py +26 -0
  41. nexo/schemas/operation/enums.py +168 -0
  42. nexo/schemas/operation/extractor.py +36 -0
  43. nexo/schemas/operation/mixins.py +53 -0
  44. nexo/schemas/operation/request.py +1066 -0
  45. nexo/schemas/operation/resource.py +839 -0
  46. nexo/schemas/operation/system.py +55 -0
  47. nexo/schemas/operation/websocket.py +55 -0
  48. nexo/schemas/pagination.py +67 -0
  49. nexo/schemas/parameter.py +60 -0
  50. nexo/schemas/payload.py +116 -0
  51. nexo/schemas/resource.py +64 -0
  52. nexo/schemas/response.py +1041 -0
  53. nexo/schemas/security/__init__.py +0 -0
  54. nexo/schemas/security/api_key.py +63 -0
  55. nexo/schemas/security/authentication.py +848 -0
  56. nexo/schemas/security/authorization.py +922 -0
  57. nexo/schemas/security/enums.py +32 -0
  58. nexo/schemas/security/impersonation.py +179 -0
  59. nexo/schemas/security/token.py +402 -0
  60. nexo/schemas/security/types.py +17 -0
  61. nexo/schemas/success/__init__.py +0 -0
  62. nexo/schemas/success/descriptor.py +100 -0
  63. nexo/schemas/success/enums.py +23 -0
  64. nexo/schemas/user_agent.py +46 -0
  65. nexo_schemas-0.0.16.dist-info/METADATA +87 -0
  66. nexo_schemas-0.0.16.dist-info/RECORD +69 -0
  67. nexo_schemas-0.0.16.dist-info/WHEEL +5 -0
  68. nexo_schemas-0.0.16.dist-info/licenses/LICENSE +21 -0
  69. nexo_schemas-0.0.16.dist-info/top_level.txt +1 -0
@@ -0,0 +1,387 @@
1
+ from datetime import date
2
+ from pydantic import BaseModel, Field, computed_field
3
+ from typing import Annotated, Generic, Self, TypeVar
4
+ from uuid import UUID as PythonUUID
5
+ from nexo.types.datetime import OptDate, OptDateT, OptListOfDatesT
6
+ from nexo.types.enum import StrEnumT, OptStrEnumT
7
+ from nexo.types.integer import OptIntT, OptListOfIntsT
8
+ from nexo.types.misc import (
9
+ OptIntOrUUIDT,
10
+ OptListOfIntsOrUUIDsT,
11
+ )
12
+ from nexo.types.string import OptStrT, OptListOfStrsT
13
+ from nexo.types.uuid import OptUUIDT, OptListOfUUIDsT
14
+
15
+
16
+ class IdentifierType(BaseModel, Generic[StrEnumT]):
17
+ type: StrEnumT = Field(..., description="Identifier's type")
18
+
19
+
20
+ IdentifierValueT = TypeVar("IdentifierValueT")
21
+
22
+
23
+ class IdentifierValue(BaseModel, Generic[IdentifierValueT]):
24
+ value: IdentifierValueT = Field(..., description="Identifier's value")
25
+
26
+
27
+ class Identifier(
28
+ IdentifierValue[IdentifierValueT],
29
+ IdentifierType[StrEnumT],
30
+ Generic[StrEnumT, IdentifierValueT],
31
+ ):
32
+ pass
33
+
34
+
35
+ IdentifierT = TypeVar("IdentifierT", bound=Identifier)
36
+
37
+
38
+ class IdentifierMixin(BaseModel, Generic[IdentifierT]):
39
+ identifier: Annotated[IdentifierT, Field(..., description="Identifier")]
40
+
41
+
42
+ # Id
43
+ class Id(BaseModel, Generic[OptIntOrUUIDT]):
44
+ id: OptIntOrUUIDT = Field(..., description="Id")
45
+
46
+
47
+ class IntId(BaseModel, Generic[OptIntT]):
48
+ id: OptIntT = Field(..., description="Id (Integer)", ge=1)
49
+
50
+
51
+ class UUIDId(BaseModel, Generic[OptUUIDT]):
52
+ id: OptUUIDT = Field(..., description="Id (UUID)")
53
+
54
+
55
+ # Ids
56
+ class Ids(BaseModel, Generic[OptListOfIntsOrUUIDsT]):
57
+ ids: OptListOfIntsOrUUIDsT = Field(..., description="Ids")
58
+
59
+
60
+ class IntIds(BaseModel, Generic[OptListOfIntsT]):
61
+ ids: OptListOfIntsT = Field(..., description="Ids (Integers)")
62
+
63
+
64
+ class UUIDIds(BaseModel, Generic[OptListOfUUIDsT]):
65
+ ids: OptListOfUUIDsT = Field(..., description="Ids (UUIDs)")
66
+
67
+
68
+ # UUID
69
+ class UUID(BaseModel, Generic[OptUUIDT]):
70
+ uuid: OptUUIDT = Field(..., description="UUID")
71
+
72
+
73
+ class UUIDs(BaseModel, Generic[OptListOfUUIDsT]):
74
+ uuids: OptListOfUUIDsT = Field(..., description="UUIDs")
75
+
76
+
77
+ # Identifier
78
+ class DataIdentifier(
79
+ UUID[PythonUUID],
80
+ IntId[int],
81
+ ):
82
+ pass
83
+
84
+
85
+ class EntityType(BaseModel, Generic[OptStrEnumT]):
86
+ type: Annotated[OptStrEnumT, Field(..., description="Entity's type")]
87
+
88
+
89
+ class EntityIdentifier(
90
+ EntityType[OptStrEnumT], UUID[PythonUUID], IntId[int], Generic[OptStrEnumT]
91
+ ):
92
+ pass
93
+
94
+
95
+ EntityIdentifierT = TypeVar("EntityIdentifierT", bound=EntityIdentifier)
96
+
97
+
98
+ class IdCard(BaseModel, Generic[OptStrT]):
99
+ id_card: OptStrT = Field(..., description="ID Card")
100
+
101
+
102
+ class IdCards(BaseModel, Generic[OptListOfStrsT]):
103
+ id_cards: OptListOfStrsT = Field(..., description="ID Cards")
104
+
105
+
106
+ class Passport(BaseModel, Generic[OptStrT]):
107
+ passport: OptStrT = Field(..., description="Passport")
108
+
109
+
110
+ class Passports(BaseModel, Generic[OptListOfStrsT]):
111
+ passports: OptListOfStrsT = Field(..., description="Passports")
112
+
113
+
114
+ class Key(BaseModel, Generic[OptStrT]):
115
+ key: OptStrT = Field(..., description="Key")
116
+
117
+
118
+ class Keys(BaseModel, Generic[OptListOfStrsT]):
119
+ keys: OptListOfStrsT = Field(..., description="Keys")
120
+
121
+
122
+ class FullName(BaseModel, Generic[OptStrT]):
123
+ full_name: OptStrT = Field(..., description="Full Name")
124
+
125
+
126
+ class FullNames(BaseModel, Generic[OptListOfStrsT]):
127
+ full_names: OptListOfStrsT = Field(..., description="Full Names")
128
+
129
+
130
+ class Name(BaseModel, Generic[OptStrT]):
131
+ name: OptStrT = Field(..., description="Name")
132
+
133
+
134
+ class Names(BaseModel, Generic[OptListOfStrsT]):
135
+ names: OptListOfStrsT = Field(..., description="Names")
136
+
137
+
138
+ class Age(BaseModel):
139
+ raw: Annotated[float, Field(0, description="Age", ge=0.0)] = 0
140
+ year: Annotated[int, Field(0, description="Year", ge=0)] = 0
141
+ month: Annotated[int, Field(0, description="Month", ge=0)] = 0
142
+ day: Annotated[int, Field(0, description="Day", ge=0)] = 0
143
+
144
+ @classmethod
145
+ def from_date_of_birth(cls, date_of_birth: OptDate) -> Self:
146
+ if date_of_birth is None:
147
+ return cls()
148
+
149
+ today = date.today()
150
+
151
+ # Compute Y/M/D difference
152
+ y = today.year - date_of_birth.year
153
+ m = today.month - date_of_birth.month
154
+ d = today.day - date_of_birth.day
155
+
156
+ # Normalize negative days/months
157
+ if d < 0:
158
+ m -= 1
159
+ # Approx: last month's number of days
160
+ # If precision needed you can use calendar.monthrange
161
+ prev_month = (today.month - 1) or 12
162
+ prev_year = today.year if today.month > 1 else today.year - 1
163
+ from calendar import monthrange
164
+
165
+ d += monthrange(prev_year, prev_month)[1]
166
+
167
+ if m < 0:
168
+ y -= 1
169
+ m += 12
170
+
171
+ raw = y + m / 12 + d / 365.25
172
+
173
+ return cls(raw=raw, year=y, month=m, day=d)
174
+
175
+
176
+ OptAge = Age | None
177
+ OptAgeT = TypeVar("OptAgeT", bound=OptAge)
178
+
179
+
180
+ class AgeMixin(BaseModel):
181
+ age: Annotated[Age, Field(default_factory=Age, description="Age")]
182
+
183
+
184
+ class BirthDate(BaseModel, Generic[OptDateT]):
185
+ birth_date: OptDateT = Field(..., description="Birth Date")
186
+
187
+ @computed_field
188
+ @property
189
+ def age(self) -> Age:
190
+ return Age.from_date_of_birth(self.birth_date)
191
+
192
+
193
+ class BirthDates(BaseModel, Generic[OptListOfDatesT]):
194
+ birth_dates: OptListOfDatesT = Field(..., description="Birth Dates")
195
+
196
+
197
+ class DateOfBirth(BaseModel, Generic[OptDateT]):
198
+ date_of_birth: OptDateT = Field(..., description="Date of Birth")
199
+
200
+ @computed_field
201
+ @property
202
+ def age(self) -> Age:
203
+ return Age.from_date_of_birth(self.date_of_birth)
204
+
205
+
206
+ class DateOfBirths(BaseModel, Generic[OptListOfDatesT]):
207
+ date_of_births: OptListOfDatesT = Field(..., description="Date of Births")
208
+
209
+
210
+ class BirthPlace(BaseModel, Generic[OptStrT]):
211
+ birth_place: OptStrT = Field(..., description="Birth Place")
212
+
213
+
214
+ class BirthPlaces(BaseModel, Generic[OptListOfStrsT]):
215
+ birth_places: OptListOfStrsT = Field(..., description="Birth Places")
216
+
217
+
218
+ class PlaceOfBirth(BaseModel, Generic[OptStrT]):
219
+ place_of_birth: OptStrT = Field(..., description="Place of Birth")
220
+
221
+
222
+ class PlaceOfBirths(BaseModel, Generic[OptListOfStrsT]):
223
+ place_of_births: OptListOfStrsT = Field(..., description="Place of Births")
224
+
225
+
226
+ # ----- ----- ----- Organization ID ----- ----- ----- #
227
+
228
+
229
+ class OrganizationId(BaseModel, Generic[OptIntOrUUIDT]):
230
+ organization_id: OptIntOrUUIDT = Field(..., description="Organization's ID")
231
+
232
+
233
+ class IntOrganizationId(BaseModel, Generic[OptIntT]):
234
+ organization_id: OptIntT = Field(..., description="Organization's ID", ge=1)
235
+
236
+
237
+ class UUIDOrganizationId(BaseModel, Generic[OptUUIDT]):
238
+ organization_id: OptUUIDT = Field(..., description="Organization's ID")
239
+
240
+
241
+ class OrganizationIds(BaseModel, Generic[OptListOfIntsOrUUIDsT]):
242
+ organization_ids: OptListOfIntsOrUUIDsT = Field(
243
+ ..., description="Organization's IDs"
244
+ )
245
+
246
+
247
+ class IntOrganizationIds(BaseModel, Generic[OptListOfIntsT]):
248
+ organization_ids: OptListOfIntsT = Field(..., description="Organization's IDs")
249
+
250
+
251
+ class UUIDOrganizationIds(BaseModel, Generic[OptListOfUUIDsT]):
252
+ organization_ids: OptListOfUUIDsT = Field(..., description="Organization's IDs")
253
+
254
+
255
+ # ----- ----- ----- Parent ID ----- ----- ----- #
256
+
257
+
258
+ class ParentId(BaseModel, Generic[OptIntOrUUIDT]):
259
+ parent_id: OptIntOrUUIDT = Field(..., description="Parent's ID")
260
+
261
+
262
+ class IntParentId(BaseModel, Generic[OptIntT]):
263
+ parent_id: OptIntT = Field(..., description="Parent's ID", ge=1)
264
+
265
+
266
+ class UUIDParentId(BaseModel, Generic[OptUUIDT]):
267
+ parent_id: OptUUIDT = Field(..., description="Parent's ID")
268
+
269
+
270
+ class ParentIds(BaseModel, Generic[OptListOfIntsOrUUIDsT]):
271
+ parent_ids: OptListOfIntsOrUUIDsT = Field(..., description="Parent's IDs")
272
+
273
+
274
+ class IntParentIds(BaseModel, Generic[OptListOfIntsT]):
275
+ parent_ids: OptListOfIntsT = Field(..., description="Parent's IDs")
276
+
277
+
278
+ class UUIDParentIds(BaseModel, Generic[OptListOfUUIDsT]):
279
+ parent_ids: OptListOfUUIDsT = Field(..., description="Parent's IDs")
280
+
281
+
282
+ # ----- ----- ----- Patient ID ----- ----- ----- #
283
+
284
+
285
+ class PatientId(BaseModel, Generic[OptIntOrUUIDT]):
286
+ patient_id: OptIntOrUUIDT = Field(..., description="Patient's ID")
287
+
288
+
289
+ class IntPatientId(BaseModel, Generic[OptIntT]):
290
+ patient_id: OptIntT = Field(..., description="Patient's ID", ge=1)
291
+
292
+
293
+ class UUIDPatientId(BaseModel, Generic[OptUUIDT]):
294
+ patient_id: OptUUIDT = Field(..., description="Patient's ID")
295
+
296
+
297
+ class PatientIds(BaseModel, Generic[OptListOfIntsOrUUIDsT]):
298
+ patient_ids: OptListOfIntsOrUUIDsT = Field(..., description="Patient's IDs")
299
+
300
+
301
+ class IntPatientIds(BaseModel, Generic[OptListOfIntsT]):
302
+ patient_ids: OptListOfIntsT = Field(..., description="Patient's IDs")
303
+
304
+
305
+ class UUIDPatientIds(BaseModel, Generic[OptListOfUUIDsT]):
306
+ patient_ids: OptListOfUUIDsT = Field(..., description="Patient's IDs")
307
+
308
+
309
+ # ----- ----- ----- Source ID ----- ----- ----- #
310
+
311
+
312
+ class SourceId(BaseModel, Generic[OptIntOrUUIDT]):
313
+ source_id: OptIntOrUUIDT = Field(..., description="Source's ID")
314
+
315
+
316
+ class IntSourceId(BaseModel, Generic[OptIntT]):
317
+ source_id: OptIntT = Field(..., description="Source's ID", ge=1)
318
+
319
+
320
+ class UUIDSourceId(BaseModel, Generic[OptUUIDT]):
321
+ source_id: OptUUIDT = Field(..., description="Source's ID")
322
+
323
+
324
+ class SourceIds(BaseModel, Generic[OptListOfIntsOrUUIDsT]):
325
+ source_ids: OptListOfIntsOrUUIDsT = Field(..., description="Source's IDs")
326
+
327
+
328
+ class IntSourceIds(BaseModel, Generic[OptListOfIntsT]):
329
+ source_ids: OptListOfIntsT = Field(..., description="Source's IDs")
330
+
331
+
332
+ class UUIDSourceIds(BaseModel, Generic[OptListOfUUIDsT]):
333
+ source_ids: OptListOfUUIDsT = Field(..., description="Source's IDs")
334
+
335
+
336
+ # ----- ----- ----- Target ID ----- ----- ----- #
337
+
338
+
339
+ class TargetId(BaseModel, Generic[OptIntOrUUIDT]):
340
+ target_id: OptIntOrUUIDT = Field(..., description="Target's ID")
341
+
342
+
343
+ class IntTargetId(BaseModel, Generic[OptIntT]):
344
+ target_id: OptIntT = Field(..., description="Target's ID", ge=1)
345
+
346
+
347
+ class UUIDTargetId(BaseModel, Generic[OptUUIDT]):
348
+ target_id: OptUUIDT = Field(..., description="Target's ID")
349
+
350
+
351
+ class TargetIds(BaseModel, Generic[OptListOfIntsOrUUIDsT]):
352
+ target_ids: OptListOfIntsOrUUIDsT = Field(..., description="Target's IDs")
353
+
354
+
355
+ class IntTargetIds(BaseModel, Generic[OptListOfIntsT]):
356
+ target_ids: OptListOfIntsT = Field(..., description="Target's IDs")
357
+
358
+
359
+ class UUIDTargetIds(BaseModel, Generic[OptListOfUUIDsT]):
360
+ target_ids: OptListOfUUIDsT = Field(..., description="Target's IDs")
361
+
362
+
363
+ # ----- ----- ----- User ID ----- ----- ----- #
364
+
365
+
366
+ class UserId(BaseModel, Generic[OptIntOrUUIDT]):
367
+ user_id: OptIntOrUUIDT = Field(..., description="User's ID")
368
+
369
+
370
+ class IntUserId(BaseModel, Generic[OptIntT]):
371
+ user_id: OptIntT = Field(..., description="User's ID", ge=1)
372
+
373
+
374
+ class UUIDUserId(BaseModel, Generic[OptUUIDT]):
375
+ user_id: OptUUIDT = Field(..., description="User's ID")
376
+
377
+
378
+ class UserIds(BaseModel, Generic[OptListOfIntsOrUUIDsT]):
379
+ user_ids: OptListOfIntsOrUUIDsT = Field(..., description="User's IDs")
380
+
381
+
382
+ class IntUserIds(BaseModel, Generic[OptListOfIntsT]):
383
+ user_ids: OptListOfIntsT = Field(..., description="User's IDs")
384
+
385
+
386
+ class UUIDUserIds(BaseModel, Generic[OptListOfUUIDsT]):
387
+ user_ids: OptListOfUUIDsT = Field(..., description="User's IDs")
@@ -0,0 +1,50 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Annotated, Generic
3
+ from nexo.enums.status import (
4
+ ListOfDataStatuses,
5
+ OptListOfDataStatuses,
6
+ SimpleDataStatusesMixin,
7
+ FULL_DATA_STATUSES,
8
+ )
9
+ from nexo.types.enum import OptListOfStrEnumsT
10
+ from nexo.types.string import OptStr
11
+
12
+
13
+ class OptionalSimpleDataStatusesMixin(SimpleDataStatusesMixin[OptListOfDataStatuses]):
14
+ statuses: Annotated[
15
+ OptListOfDataStatuses,
16
+ Field(None, description="Data statuses", min_length=1),
17
+ ] = None
18
+
19
+
20
+ class MandatorySimpleDataStatusesMixin(SimpleDataStatusesMixin[ListOfDataStatuses]):
21
+ statuses: Annotated[
22
+ ListOfDataStatuses,
23
+ Field(FULL_DATA_STATUSES, description="Data statuses", min_length=1),
24
+ ] = FULL_DATA_STATUSES
25
+
26
+
27
+ class Search(BaseModel):
28
+ search: Annotated[OptStr, Field(None, description="Search string")] = None
29
+
30
+
31
+ class UseCache(BaseModel):
32
+ use_cache: Annotated[bool, Field(True, description="Whether to use cache")] = True
33
+
34
+
35
+ class IncludeURL(BaseModel):
36
+ include_url: Annotated[bool, Field(False, description="Whether to include URL")] = (
37
+ False
38
+ )
39
+
40
+
41
+ class Include(BaseModel, Generic[OptListOfStrEnumsT]):
42
+ include: Annotated[OptListOfStrEnumsT, Field(..., description="Included field(s)")]
43
+
44
+
45
+ class Exclude(BaseModel, Generic[OptListOfStrEnumsT]):
46
+ exclude: Annotated[OptListOfStrEnumsT, Field(..., description="Excluded field(s)")]
47
+
48
+
49
+ class Expand(BaseModel, Generic[OptListOfStrEnumsT]):
50
+ expand: Annotated[OptListOfStrEnumsT, Field(..., description="Expanded field(s)")]
@@ -0,0 +1,40 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Annotated, Generic
3
+ from nexo.types.service import (
4
+ OptServiceKeyT,
5
+ OptServiceKeysT,
6
+ OptServiceNameT,
7
+ OptServiceNamesT,
8
+ )
9
+
10
+
11
+ class SimpleServiceKeyMixin(BaseModel, Generic[OptServiceKeyT]):
12
+ key: Annotated[OptServiceKeyT, Field(..., description="Service Key")]
13
+
14
+
15
+ class FullServiceKeyMixin(BaseModel, Generic[OptServiceKeyT]):
16
+ service_key: Annotated[OptServiceKeyT, Field(..., description="Service Key")]
17
+
18
+
19
+ class SimpleServiceKeysMixin(BaseModel, Generic[OptServiceKeysT]):
20
+ keys: Annotated[OptServiceKeysT, Field(..., description="Service Keys")]
21
+
22
+
23
+ class FullServiceKeysMixin(BaseModel, Generic[OptServiceKeysT]):
24
+ service_keys: Annotated[OptServiceKeysT, Field(..., description="Service Keys")]
25
+
26
+
27
+ class SimpleServiceNameMixin(BaseModel, Generic[OptServiceNameT]):
28
+ name: Annotated[OptServiceNameT, Field(..., description="Service Name")]
29
+
30
+
31
+ class FullServiceNameMixin(BaseModel, Generic[OptServiceNameT]):
32
+ service_name: Annotated[OptServiceNameT, Field(..., description="Service Name")]
33
+
34
+
35
+ class SimpleServiceNamesMixin(BaseModel, Generic[OptServiceNamesT]):
36
+ names: Annotated[OptServiceNamesT, Field(..., description="Service Names")]
37
+
38
+
39
+ class FullServiceNamesMixin(BaseModel, Generic[OptServiceNamesT]):
40
+ service_names: Annotated[OptServiceNamesT, Field(..., description="Service Names")]
@@ -0,0 +1,111 @@
1
+ import re
2
+ from pydantic import BaseModel, Field, field_validator
3
+ from typing import Annotated, TypeGuard, overload
4
+ from nexo.enums.order import Order as OrderEnum
5
+ from nexo.types.string import ListOfStrs
6
+ from .general import Order
7
+ from .identity import Name
8
+
9
+
10
+ SORT_COLUMN_REGEX = r"^(?P<name>[a-z_]+)\.(?P<order>asc|desc)$"
11
+ SORT_COLUMN_PATTERN = re.compile(SORT_COLUMN_REGEX)
12
+
13
+
14
+ class SortColumn(
15
+ Order[OrderEnum],
16
+ Name[str],
17
+ ):
18
+ name: Annotated[str, Field(..., description="Name", min_length=1)]
19
+
20
+ @classmethod
21
+ def from_string(cls, sort: str) -> "SortColumn":
22
+ match = SORT_COLUMN_PATTERN.match(sort)
23
+ if not match:
24
+ raise ValueError(f"Invalid sort format: {sort!r}")
25
+
26
+ name = match.group("name")
27
+ order_raw = match.group("order")
28
+ order = OrderEnum(order_raw)
29
+
30
+ return cls(name=name, order=order)
31
+
32
+ def to_string(self) -> str:
33
+ return f"{self.name}.{self.order}"
34
+
35
+
36
+ OptSortColumn = SortColumn | None
37
+ ListOfSortColumns = list[SortColumn]
38
+ OptListOfSortColumns = ListOfSortColumns | None
39
+
40
+
41
+ AnySorts = ListOfSortColumns | ListOfStrs
42
+
43
+
44
+ def is_sort_columns(
45
+ sorts: AnySorts,
46
+ ) -> TypeGuard[ListOfSortColumns]:
47
+ return all(isinstance(sort, SortColumn) for sort in sorts)
48
+
49
+
50
+ def is_sorts(
51
+ sorts: AnySorts,
52
+ ) -> TypeGuard[ListOfStrs]:
53
+ return all(isinstance(sort, str) for sort in sorts)
54
+
55
+
56
+ class Sorts(BaseModel):
57
+ sorts: Annotated[
58
+ ListOfStrs,
59
+ Field(
60
+ ["id.asc"],
61
+ description="Column sorts with '<COLUMN_NAME>.<ASC|DESC>' format",
62
+ ),
63
+ ] = ["id.asc"]
64
+
65
+ @field_validator("sorts", mode="after")
66
+ @classmethod
67
+ def validate_sorts_pattern(cls, value: ListOfStrs) -> ListOfStrs:
68
+ for v in value:
69
+ match = SORT_COLUMN_PATTERN.match(v)
70
+ if not match:
71
+ raise ValueError(f"Invalid sort column format: {v!r}")
72
+ return value
73
+
74
+ @classmethod
75
+ def from_sort_columns(cls, sort_columns: ListOfSortColumns) -> "Sorts":
76
+ return cls(sorts=[sort.to_string() for sort in sort_columns])
77
+
78
+ @property
79
+ def sort_columns(self) -> ListOfSortColumns:
80
+ return [SortColumn.from_string(sort) for sort in self.sorts]
81
+
82
+
83
+ class SortColumns(BaseModel):
84
+ sort_columns: Annotated[
85
+ ListOfSortColumns,
86
+ Field(
87
+ [SortColumn(name="id", order=OrderEnum.ASC)],
88
+ description="List of columns to be sorted",
89
+ ),
90
+ ] = [SortColumn(name="id", order=OrderEnum.ASC)]
91
+
92
+ @classmethod
93
+ def from_sorts(cls, sorts: ListOfStrs) -> "SortColumns":
94
+ return cls(sort_columns=[SortColumn.from_string(sort) for sort in sorts])
95
+
96
+ @property
97
+ def sorts(self) -> ListOfStrs:
98
+ return [sort.to_string() for sort in self.sort_columns]
99
+
100
+
101
+ @overload
102
+ def convert(sorts: ListOfSortColumns) -> ListOfStrs: ...
103
+ @overload
104
+ def convert(sorts: ListOfStrs) -> ListOfSortColumns: ...
105
+ def convert(sorts: AnySorts) -> AnySorts:
106
+ if is_sort_columns(sorts):
107
+ return [sort.to_string() for sort in sorts]
108
+ elif is_sorts(sorts):
109
+ return [SortColumn.from_string(sort) for sort in sorts]
110
+ else:
111
+ raise ValueError("Sort type is neither SortColumn nor string")