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.
- nexo/schemas/__init__.py +0 -0
- nexo/schemas/application.py +292 -0
- nexo/schemas/connection.py +134 -0
- nexo/schemas/data.py +27 -0
- nexo/schemas/document.py +237 -0
- nexo/schemas/error/__init__.py +476 -0
- nexo/schemas/error/constants.py +50 -0
- nexo/schemas/error/descriptor.py +354 -0
- nexo/schemas/error/enums.py +40 -0
- nexo/schemas/error/metadata.py +15 -0
- nexo/schemas/error/spec.py +312 -0
- nexo/schemas/exception/__init__.py +0 -0
- nexo/schemas/exception/exc.py +911 -0
- nexo/schemas/exception/factory.py +1928 -0
- nexo/schemas/exception/handlers.py +110 -0
- nexo/schemas/google.py +14 -0
- nexo/schemas/key/__init__.py +0 -0
- nexo/schemas/key/rsa.py +131 -0
- nexo/schemas/metadata.py +21 -0
- nexo/schemas/mixins/__init__.py +0 -0
- nexo/schemas/mixins/filter.py +140 -0
- nexo/schemas/mixins/general.py +65 -0
- nexo/schemas/mixins/hierarchy.py +19 -0
- nexo/schemas/mixins/identity.py +387 -0
- nexo/schemas/mixins/parameter.py +50 -0
- nexo/schemas/mixins/service.py +40 -0
- nexo/schemas/mixins/sort.py +111 -0
- nexo/schemas/mixins/timestamp.py +192 -0
- nexo/schemas/model.py +240 -0
- nexo/schemas/operation/__init__.py +0 -0
- nexo/schemas/operation/action/__init__.py +9 -0
- nexo/schemas/operation/action/base.py +14 -0
- nexo/schemas/operation/action/resource.py +371 -0
- nexo/schemas/operation/action/status.py +8 -0
- nexo/schemas/operation/action/system.py +6 -0
- nexo/schemas/operation/action/websocket.py +6 -0
- nexo/schemas/operation/base.py +289 -0
- nexo/schemas/operation/constants.py +18 -0
- nexo/schemas/operation/context.py +68 -0
- nexo/schemas/operation/dependency.py +26 -0
- nexo/schemas/operation/enums.py +168 -0
- nexo/schemas/operation/extractor.py +36 -0
- nexo/schemas/operation/mixins.py +53 -0
- nexo/schemas/operation/request.py +1066 -0
- nexo/schemas/operation/resource.py +839 -0
- nexo/schemas/operation/system.py +55 -0
- nexo/schemas/operation/websocket.py +55 -0
- nexo/schemas/pagination.py +67 -0
- nexo/schemas/parameter.py +60 -0
- nexo/schemas/payload.py +116 -0
- nexo/schemas/resource.py +64 -0
- nexo/schemas/response.py +1041 -0
- nexo/schemas/security/__init__.py +0 -0
- nexo/schemas/security/api_key.py +63 -0
- nexo/schemas/security/authentication.py +848 -0
- nexo/schemas/security/authorization.py +922 -0
- nexo/schemas/security/enums.py +32 -0
- nexo/schemas/security/impersonation.py +179 -0
- nexo/schemas/security/token.py +402 -0
- nexo/schemas/security/types.py +17 -0
- nexo/schemas/success/__init__.py +0 -0
- nexo/schemas/success/descriptor.py +100 -0
- nexo/schemas/success/enums.py +23 -0
- nexo/schemas/user_agent.py +46 -0
- nexo_schemas-0.0.16.dist-info/METADATA +87 -0
- nexo_schemas-0.0.16.dist-info/RECORD +69 -0
- nexo_schemas-0.0.16.dist-info/WHEEL +5 -0
- nexo_schemas-0.0.16.dist-info/licenses/LICENSE +21 -0
- nexo_schemas-0.0.16.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from typing import Awaitable, Callable
|
|
2
|
+
from fastapi import Request, Response, status
|
|
3
|
+
from fastapi.encoders import jsonable_encoder
|
|
4
|
+
from fastapi.exceptions import (
|
|
5
|
+
HTTPException,
|
|
6
|
+
RequestValidationError,
|
|
7
|
+
WebSocketRequestValidationError,
|
|
8
|
+
ResponseValidationError,
|
|
9
|
+
)
|
|
10
|
+
from fastapi.requests import HTTPConnection
|
|
11
|
+
from fastapi.responses import JSONResponse
|
|
12
|
+
from pydantic import ValidationError
|
|
13
|
+
from starlette.authentication import AuthenticationError
|
|
14
|
+
from nexo.logging.logger import Exception as ExceptionLogger
|
|
15
|
+
from nexo.utils.exception import extract_details
|
|
16
|
+
from ..error.constants import ERROR_CODE_STATUS_CODE_MAP
|
|
17
|
+
from ..error.enums import ErrorCode
|
|
18
|
+
from ..google import ListOfPublisherHandlers
|
|
19
|
+
from ..response import (
|
|
20
|
+
UnauthorizedResponse,
|
|
21
|
+
UnprocessableEntityResponse,
|
|
22
|
+
InternalServerErrorResponse,
|
|
23
|
+
ErrorResponseFactory,
|
|
24
|
+
)
|
|
25
|
+
from .exc import MaleoException, AnyException
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def authentication_error_handler(
|
|
29
|
+
conn: HTTPConnection, exc: AuthenticationError
|
|
30
|
+
) -> JSONResponse:
|
|
31
|
+
return JSONResponse(
|
|
32
|
+
content=UnauthorizedResponse(other=extract_details(exc)).model_dump(
|
|
33
|
+
mode="json"
|
|
34
|
+
),
|
|
35
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def validation_error_handler(
|
|
40
|
+
request: Request,
|
|
41
|
+
exc: (
|
|
42
|
+
RequestValidationError
|
|
43
|
+
| WebSocketRequestValidationError
|
|
44
|
+
| ResponseValidationError
|
|
45
|
+
| ValidationError
|
|
46
|
+
),
|
|
47
|
+
) -> JSONResponse:
|
|
48
|
+
return JSONResponse(
|
|
49
|
+
content=UnprocessableEntityResponse(
|
|
50
|
+
other=jsonable_encoder(exc.errors())
|
|
51
|
+
).model_dump(mode="json"),
|
|
52
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
|
57
|
+
return JSONResponse(
|
|
58
|
+
content=(
|
|
59
|
+
ErrorResponseFactory.cls_from_code(exc.status_code)(
|
|
60
|
+
other=extract_details(exc)
|
|
61
|
+
).model_dump(mode="json")
|
|
62
|
+
),
|
|
63
|
+
status_code=exc.status_code,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_maleo_exception_handler(
|
|
68
|
+
logger: ExceptionLogger,
|
|
69
|
+
publishers: ListOfPublisherHandlers = [],
|
|
70
|
+
) -> Callable[[Request, MaleoException], Awaitable[JSONResponse]]:
|
|
71
|
+
async def maleo_exception_handler(
|
|
72
|
+
request: Request, exc: MaleoException | AnyException
|
|
73
|
+
) -> JSONResponse:
|
|
74
|
+
exc.log_and_publish_operation(logger, publishers)
|
|
75
|
+
|
|
76
|
+
return JSONResponse(
|
|
77
|
+
content=exc.response.model_dump(mode="json"),
|
|
78
|
+
status_code=exc.error.spec.status_code,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return maleo_exception_handler
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def general_exception_handler(request: Request, exc: Exception) -> Response:
|
|
85
|
+
other = extract_details(exc)
|
|
86
|
+
|
|
87
|
+
# Get the first arg as a potential ErrorCode
|
|
88
|
+
code = exc.args[0] if exc.args else None
|
|
89
|
+
|
|
90
|
+
if isinstance(code, ErrorCode):
|
|
91
|
+
error_code = code
|
|
92
|
+
elif isinstance(code, str) and code in ErrorCode:
|
|
93
|
+
error_code = ErrorCode[code]
|
|
94
|
+
else:
|
|
95
|
+
error_code = None
|
|
96
|
+
|
|
97
|
+
if error_code is not None:
|
|
98
|
+
status_code = ERROR_CODE_STATUS_CODE_MAP.get(error_code, None)
|
|
99
|
+
|
|
100
|
+
if status_code is not None:
|
|
101
|
+
response_cls = ErrorResponseFactory.cls_from_code(status_code)
|
|
102
|
+
return JSONResponse(
|
|
103
|
+
content=response_cls(other=other).model_dump(mode="json"),
|
|
104
|
+
status_code=status_code,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return JSONResponse(
|
|
108
|
+
content=InternalServerErrorResponse(other=other).model_dump(mode="json"),
|
|
109
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
110
|
+
)
|
nexo/schemas/google.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from google.cloud.pubsub_v1 import PublisherClient
|
|
2
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PublisherHandler(BaseModel):
|
|
7
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
8
|
+
|
|
9
|
+
client: Annotated[PublisherClient, Field(..., description="Publisher client")]
|
|
10
|
+
project_id: Annotated[str, Field(..., description="Project ID")]
|
|
11
|
+
topic_id: Annotated[str, Field(..., description="Topic ID")]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ListOfPublisherHandlers = list[PublisherHandler]
|
|
File without changes
|
nexo/schemas/key/rsa.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from Crypto.Cipher import PKCS1_OAEP
|
|
3
|
+
from Crypto.PublicKey import RSA
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
from typing import Annotated, Self
|
|
7
|
+
from nexo.crypto.key.rsa.enums import KeyType
|
|
8
|
+
from nexo.crypto.key.rsa.loader import with_cryptography
|
|
9
|
+
from nexo.types.misc import PathOrStr
|
|
10
|
+
from nexo.types.string import OptStr
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Key(BaseModel, ABC):
|
|
14
|
+
raw: Annotated[str, Field(..., description="Raw key")]
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def _validate_raw(self):
|
|
18
|
+
"""Validate raw key"""
|
|
19
|
+
|
|
20
|
+
@model_validator(mode="after")
|
|
21
|
+
def validate_raw(self) -> Self:
|
|
22
|
+
self._validate_raw()
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
@cached_property
|
|
26
|
+
def as_rsa(self) -> RSA.RsaKey:
|
|
27
|
+
"""Convert raw to RSA"""
|
|
28
|
+
self._validate_raw()
|
|
29
|
+
passphrase = getattr(self, "password", None)
|
|
30
|
+
return RSA.import_key(extern_key=self.raw, passphrase=passphrase)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Private(Key):
|
|
34
|
+
password: Annotated[OptStr, Field(None, description="Key's password")] = None
|
|
35
|
+
|
|
36
|
+
def _validate_raw(self):
|
|
37
|
+
if not RSA.import_key(
|
|
38
|
+
extern_key=self.raw, passphrase=self.password
|
|
39
|
+
).has_private():
|
|
40
|
+
raise ValueError(
|
|
41
|
+
"Invalid key type, the private key did not have private inside it"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Public(Key):
|
|
46
|
+
def _validate_raw(self):
|
|
47
|
+
if RSA.import_key(extern_key=self.raw).has_private():
|
|
48
|
+
raise ValueError("Invalid key type, the public key had private inside it")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Keys(BaseModel):
|
|
52
|
+
private: Annotated[Private, Field(..., description="Private key")]
|
|
53
|
+
public: Annotated[Public, Field(..., description="Public key")]
|
|
54
|
+
|
|
55
|
+
@model_validator(mode="after")
|
|
56
|
+
def validate_complete_keys(self) -> Self:
|
|
57
|
+
try:
|
|
58
|
+
# Import private key with password
|
|
59
|
+
private_key = self.private.as_rsa
|
|
60
|
+
|
|
61
|
+
# Import public key
|
|
62
|
+
public_key = self.public.as_rsa
|
|
63
|
+
|
|
64
|
+
# Validate keys match by comparing public components
|
|
65
|
+
if (
|
|
66
|
+
private_key.publickey().n != public_key.n
|
|
67
|
+
or private_key.publickey().e != public_key.e
|
|
68
|
+
):
|
|
69
|
+
raise ValueError("Public key does not match the private key")
|
|
70
|
+
|
|
71
|
+
# Optional: Test encrypt/decrypt functionality
|
|
72
|
+
test_message = b"validation_test"
|
|
73
|
+
try:
|
|
74
|
+
# Encrypt with public key
|
|
75
|
+
cipher = PKCS1_OAEP.new(public_key)
|
|
76
|
+
encrypted = cipher.encrypt(test_message)
|
|
77
|
+
|
|
78
|
+
# Decrypt with private key
|
|
79
|
+
cipher = PKCS1_OAEP.new(private_key)
|
|
80
|
+
decrypted = cipher.decrypt(encrypted)
|
|
81
|
+
|
|
82
|
+
if decrypted != test_message:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"Keys do not work together for encryption/decryption"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
raise ValueError(f"Keys failed encryption/decryption test: {str(e)}")
|
|
89
|
+
|
|
90
|
+
except ValueError:
|
|
91
|
+
raise # Re-raise validation errors
|
|
92
|
+
except Exception as e:
|
|
93
|
+
raise ValueError(f"Key validation failed: {str(e)}")
|
|
94
|
+
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_path(
|
|
99
|
+
cls,
|
|
100
|
+
private: PathOrStr,
|
|
101
|
+
public: PathOrStr,
|
|
102
|
+
password: OptStr = None,
|
|
103
|
+
) -> Self:
|
|
104
|
+
raw_private = with_cryptography(
|
|
105
|
+
KeyType.PRIVATE,
|
|
106
|
+
path=private,
|
|
107
|
+
password=password,
|
|
108
|
+
)
|
|
109
|
+
raw_public = with_cryptography(
|
|
110
|
+
KeyType.PUBLIC,
|
|
111
|
+
path=public,
|
|
112
|
+
)
|
|
113
|
+
return cls(
|
|
114
|
+
private=Private(raw=raw_private, password=password),
|
|
115
|
+
public=Public(raw=raw_public),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def from_string(
|
|
120
|
+
cls,
|
|
121
|
+
private: str,
|
|
122
|
+
public: str,
|
|
123
|
+
password: OptStr = None,
|
|
124
|
+
) -> Self:
|
|
125
|
+
return cls(
|
|
126
|
+
private=Private(raw=private, password=password), public=Public(raw=public)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class KeysMixin(BaseModel):
|
|
131
|
+
keys: Annotated[Keys, Field(..., description="RSA Keys")]
|
nexo/schemas/metadata.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import Generic, TypeVar
|
|
3
|
+
from .mixins.general import Success, Descriptor, Other
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
AnyMetadataT = TypeVar("AnyMetadataT")
|
|
7
|
+
ModelMetadataT = TypeVar("ModelMetadataT", bound=BaseModel | None)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MetadataMixin(BaseModel, Generic[AnyMetadataT]):
|
|
11
|
+
metadata: AnyMetadataT = Field(..., description="Metadata")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FieldExpansionMetadata(Other, Descriptor[str], Success[bool]):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FieldExpansionMetadataMixin(BaseModel):
|
|
19
|
+
field_expansion: str | dict[str, FieldExpansionMetadata] | None = Field(
|
|
20
|
+
None, description="Field expansion metadata"
|
|
21
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
4
|
+
from typing import Annotated, Self, TypeGuard, overload
|
|
5
|
+
from nexo.types.datetime import OptDatetime
|
|
6
|
+
from nexo.types.string import ListOfStrs
|
|
7
|
+
from .identity import Name
|
|
8
|
+
from .timestamp import FromTimestamp, ToTimestamp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
DATE_FILTER_REGEX = (
|
|
12
|
+
r"^(?P<name>[a-z_]+)"
|
|
13
|
+
r"(?:\|from::(?P<from>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})))?"
|
|
14
|
+
r"(?:\|to::(?P<to>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})))?$"
|
|
15
|
+
)
|
|
16
|
+
DATE_FILTER_PATTERN = re.compile(DATE_FILTER_REGEX)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DateFilter(
|
|
20
|
+
ToTimestamp[OptDatetime],
|
|
21
|
+
FromTimestamp[OptDatetime],
|
|
22
|
+
Name[str],
|
|
23
|
+
):
|
|
24
|
+
name: Annotated[str, Field(..., description="Name", min_length=1)]
|
|
25
|
+
from_date: Annotated[OptDatetime, Field(None, description="From date")] = None
|
|
26
|
+
to_date: Annotated[OptDatetime, Field(None, description="To date")] = None
|
|
27
|
+
|
|
28
|
+
def _validate_timestamps(self):
|
|
29
|
+
if self.from_date is None and self.to_date is None:
|
|
30
|
+
raise ValueError("Either 'from_date' or 'to_date' must have value")
|
|
31
|
+
if self.from_date is not None and self.to_date is not None:
|
|
32
|
+
if self.to_date < self.from_date:
|
|
33
|
+
raise ValueError("Attribute 'from_date' can not be more than 'to_date'")
|
|
34
|
+
|
|
35
|
+
@model_validator(mode="after")
|
|
36
|
+
def validate_timestamps(self) -> Self:
|
|
37
|
+
self._validate_timestamps()
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_string(cls, filter: str) -> "DateFilter":
|
|
42
|
+
match = DATE_FILTER_PATTERN.match(filter)
|
|
43
|
+
if not match:
|
|
44
|
+
raise ValueError(f"Invalid date filter format: {filter!r}")
|
|
45
|
+
|
|
46
|
+
name = match.group("name")
|
|
47
|
+
from_raw = match.group("from")
|
|
48
|
+
to_raw = match.group("to")
|
|
49
|
+
|
|
50
|
+
from_date = datetime.fromisoformat(from_raw) if from_raw else None
|
|
51
|
+
to_date = datetime.fromisoformat(to_raw) if to_raw else None
|
|
52
|
+
|
|
53
|
+
if from_date is None and to_date is None:
|
|
54
|
+
raise ValueError("Either 'from_date' or 'to_date' must be provided")
|
|
55
|
+
|
|
56
|
+
return cls(name=name, from_date=from_date, to_date=to_date)
|
|
57
|
+
|
|
58
|
+
def to_string(self) -> str:
|
|
59
|
+
self._validate_timestamps()
|
|
60
|
+
filter_string = self.name
|
|
61
|
+
if self.from_date:
|
|
62
|
+
filter_string += f"|from::{self.from_date.isoformat()}"
|
|
63
|
+
if self.to_date:
|
|
64
|
+
filter_string += f"|to::{self.to_date.isoformat()}"
|
|
65
|
+
return filter_string
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
OptDateFilter = DateFilter | None
|
|
69
|
+
ListOfDateFilters = list[DateFilter]
|
|
70
|
+
OptListOfDateFilters = OptDateFilter | None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
AnyFilters = ListOfDateFilters | ListOfStrs
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_date_filters(
|
|
77
|
+
filters: AnyFilters,
|
|
78
|
+
) -> TypeGuard[ListOfDateFilters]:
|
|
79
|
+
return all(isinstance(filter, DateFilter) for filter in filters)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def is_filters(
|
|
83
|
+
filters: AnyFilters,
|
|
84
|
+
) -> TypeGuard[ListOfStrs]:
|
|
85
|
+
return all(isinstance(filter, str) for filter in filters)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Filters(BaseModel):
|
|
89
|
+
filters: Annotated[
|
|
90
|
+
ListOfStrs,
|
|
91
|
+
Field(
|
|
92
|
+
list[str](),
|
|
93
|
+
description="Date range filters with '<COLUMN_NAME>|from::<ISO_DATETIME>|to::<ISO_DATETIME>' format",
|
|
94
|
+
),
|
|
95
|
+
] = list[str]()
|
|
96
|
+
|
|
97
|
+
@field_validator("filters", mode="after")
|
|
98
|
+
@classmethod
|
|
99
|
+
def validate_filters_pattern(cls, value: ListOfStrs) -> ListOfStrs:
|
|
100
|
+
for v in value:
|
|
101
|
+
match = DATE_FILTER_PATTERN.match(v)
|
|
102
|
+
if not match:
|
|
103
|
+
raise ValueError(f"Invalid date filter format: {v!r}")
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_date_filters(cls, date_filters: ListOfDateFilters) -> "Filters":
|
|
108
|
+
return cls(filters=[filter.to_string() for filter in date_filters])
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def date_filters(self) -> ListOfDateFilters:
|
|
112
|
+
return [DateFilter.from_string(filter) for filter in self.filters]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class DateFilters(BaseModel):
|
|
116
|
+
date_filters: Annotated[
|
|
117
|
+
ListOfDateFilters,
|
|
118
|
+
Field(list[DateFilter](), description="Date filters to be applied"),
|
|
119
|
+
] = list[DateFilter]()
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_filters(cls, filters: ListOfStrs) -> "DateFilters":
|
|
123
|
+
return cls(date_filters=[DateFilter.from_string(filter) for filter in filters])
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def filters(self) -> ListOfStrs:
|
|
127
|
+
return [filter.to_string() for filter in self.date_filters]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@overload
|
|
131
|
+
def convert(filters: ListOfDateFilters) -> ListOfStrs: ...
|
|
132
|
+
@overload
|
|
133
|
+
def convert(filters: ListOfStrs) -> ListOfDateFilters: ...
|
|
134
|
+
def convert(filters: AnyFilters) -> AnyFilters:
|
|
135
|
+
if is_date_filters(filters):
|
|
136
|
+
return [filter.to_string() for filter in filters]
|
|
137
|
+
elif is_filters(filters):
|
|
138
|
+
return [DateFilter.from_string(filter) for filter in filters]
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError("Filter type is neither DateFilter nor string")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import Annotated, Any, Generic
|
|
3
|
+
from nexo.types.boolean import BoolT, OptBoolT
|
|
4
|
+
from nexo.types.enum import OptStrEnumT
|
|
5
|
+
from nexo.types.misc import (
|
|
6
|
+
OptFloatOrIntT,
|
|
7
|
+
OptIntOrStrEnumT,
|
|
8
|
+
OptStrOrStrEnumT,
|
|
9
|
+
OptListOfStrsOrStrEnumsT,
|
|
10
|
+
)
|
|
11
|
+
from nexo.types.string import OptStrT
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StatusCode(BaseModel):
|
|
15
|
+
status_code: Annotated[int, Field(..., description="Status code", ge=100, le=600)]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Success(BaseModel, Generic[BoolT]):
|
|
19
|
+
success: BoolT = Field(..., description="Success")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Code(BaseModel, Generic[OptStrOrStrEnumT]):
|
|
23
|
+
code: OptStrOrStrEnumT = Field(..., description="Code")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Codes(BaseModel, Generic[OptListOfStrsOrStrEnumsT]):
|
|
27
|
+
codes: OptListOfStrsOrStrEnumsT = Field(..., description="Codes")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Message(BaseModel):
|
|
31
|
+
message: str = Field(..., description="Message")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Description(BaseModel):
|
|
35
|
+
description: str = Field(..., description="Description")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Descriptor(
|
|
39
|
+
Description, Message, Code[OptStrOrStrEnumT], Generic[OptStrOrStrEnumT]
|
|
40
|
+
):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Order(BaseModel, Generic[OptIntOrStrEnumT]):
|
|
45
|
+
order: OptIntOrStrEnumT = Field(..., description="Order")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Level(BaseModel, Generic[OptStrEnumT]):
|
|
49
|
+
level: OptStrEnumT = Field(..., description="Level")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Note(BaseModel, Generic[OptStrT]):
|
|
53
|
+
note: OptStrT = Field(..., description="Note")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class IsDefault(BaseModel, Generic[OptBoolT]):
|
|
57
|
+
is_default: OptBoolT = Field(..., description="Whether is default")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Other(BaseModel):
|
|
61
|
+
other: Annotated[Any, Field(None, description="Other")] = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Age(BaseModel, Generic[OptFloatOrIntT]):
|
|
65
|
+
age: OptFloatOrIntT = Field(..., ge=0, description="Age")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import Generic
|
|
3
|
+
from nexo.types.boolean import OptBoolT
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IsRoot(BaseModel, Generic[OptBoolT]):
|
|
7
|
+
is_root: OptBoolT = Field(..., description="Whether is root")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IsParent(BaseModel, Generic[OptBoolT]):
|
|
11
|
+
is_parent: OptBoolT = Field(..., description="Whether is parent")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IsChild(BaseModel, Generic[OptBoolT]):
|
|
15
|
+
is_child: OptBoolT = Field(..., description="Whether is child")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IsLeaf(BaseModel, Generic[OptBoolT]):
|
|
19
|
+
is_leaf: OptBoolT = Field(..., description="Whether is leaf")
|