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,55 @@
|
|
|
1
|
+
from typing import Generic, Literal
|
|
2
|
+
from nexo.types.boolean import BoolT
|
|
3
|
+
from ..connection import OptConnectionContext
|
|
4
|
+
from ..error import (
|
|
5
|
+
OptAnyErrorT,
|
|
6
|
+
AnyErrorT,
|
|
7
|
+
)
|
|
8
|
+
from ..response import OptResponseT, ErrorResponseT, OptSuccessResponseT
|
|
9
|
+
from .action.system import SystemOperationAction
|
|
10
|
+
from .base import BaseOperation
|
|
11
|
+
from .enums import OperationType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SystemOperation(
|
|
15
|
+
BaseOperation[
|
|
16
|
+
SystemOperationAction,
|
|
17
|
+
None,
|
|
18
|
+
BoolT,
|
|
19
|
+
OptAnyErrorT,
|
|
20
|
+
OptConnectionContext,
|
|
21
|
+
OptResponseT,
|
|
22
|
+
None,
|
|
23
|
+
],
|
|
24
|
+
Generic[
|
|
25
|
+
BoolT,
|
|
26
|
+
OptAnyErrorT,
|
|
27
|
+
OptResponseT,
|
|
28
|
+
],
|
|
29
|
+
):
|
|
30
|
+
type: OperationType = OperationType.SYSTEM
|
|
31
|
+
resource: None = None
|
|
32
|
+
response_context: None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FailedSystemOperation(
|
|
36
|
+
SystemOperation[
|
|
37
|
+
Literal[False],
|
|
38
|
+
AnyErrorT,
|
|
39
|
+
ErrorResponseT,
|
|
40
|
+
],
|
|
41
|
+
Generic[AnyErrorT, ErrorResponseT],
|
|
42
|
+
):
|
|
43
|
+
success: Literal[False] = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SuccessfulSystemOperation(
|
|
47
|
+
SystemOperation[
|
|
48
|
+
Literal[True],
|
|
49
|
+
None,
|
|
50
|
+
OptSuccessResponseT,
|
|
51
|
+
],
|
|
52
|
+
Generic[OptSuccessResponseT],
|
|
53
|
+
):
|
|
54
|
+
success: Literal[True] = True
|
|
55
|
+
error: None = None
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Generic, Literal
|
|
2
|
+
from nexo.types.boolean import BoolT
|
|
3
|
+
from ..connection import OptConnectionContext
|
|
4
|
+
from ..error import (
|
|
5
|
+
OptAnyErrorT,
|
|
6
|
+
AnyErrorT,
|
|
7
|
+
)
|
|
8
|
+
from ..response import OptResponseT, ErrorResponseT, OptSuccessResponseT
|
|
9
|
+
from .action.websocket import WebSocketOperationAction
|
|
10
|
+
from .base import BaseOperation
|
|
11
|
+
from .enums import OperationType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WebSocketOperation(
|
|
15
|
+
BaseOperation[
|
|
16
|
+
WebSocketOperationAction,
|
|
17
|
+
None,
|
|
18
|
+
BoolT,
|
|
19
|
+
OptAnyErrorT,
|
|
20
|
+
OptConnectionContext,
|
|
21
|
+
OptResponseT,
|
|
22
|
+
None,
|
|
23
|
+
],
|
|
24
|
+
Generic[
|
|
25
|
+
BoolT,
|
|
26
|
+
OptAnyErrorT,
|
|
27
|
+
OptResponseT,
|
|
28
|
+
],
|
|
29
|
+
):
|
|
30
|
+
type: OperationType = OperationType.WEBSOCKET
|
|
31
|
+
resource: None = None
|
|
32
|
+
response_context: None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FailedWebSocketOperation(
|
|
36
|
+
WebSocketOperation[
|
|
37
|
+
Literal[False],
|
|
38
|
+
AnyErrorT,
|
|
39
|
+
ErrorResponseT,
|
|
40
|
+
],
|
|
41
|
+
Generic[AnyErrorT, ErrorResponseT],
|
|
42
|
+
):
|
|
43
|
+
success: Literal[False] = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SuccessfulWebSocketOperation(
|
|
47
|
+
WebSocketOperation[
|
|
48
|
+
Literal[True],
|
|
49
|
+
None,
|
|
50
|
+
OptSuccessResponseT,
|
|
51
|
+
],
|
|
52
|
+
Generic[OptSuccessResponseT],
|
|
53
|
+
):
|
|
54
|
+
success: Literal[True] = True
|
|
55
|
+
error: None = None
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from pydantic import BaseModel, Field, model_validator
|
|
3
|
+
from typing import Annotated, Generic, Self, TypeVar
|
|
4
|
+
from nexo.types.integer import ListOfInts, OptInt
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Limit(IntEnum):
|
|
8
|
+
LIM_10 = 10
|
|
9
|
+
LIM_20 = 20
|
|
10
|
+
LIM_50 = 50
|
|
11
|
+
LIM_100 = 100
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def choices(cls) -> ListOfInts:
|
|
15
|
+
return [e.value for e in cls]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Page(BaseModel):
|
|
19
|
+
page: Annotated[int, Field(1, ge=1, description="Page number, must be >= 1")] = 1
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FlexibleLimit(BaseModel):
|
|
23
|
+
limit: Annotated[
|
|
24
|
+
OptInt, Field(None, description="Page limit. (Optional)", ge=1)
|
|
25
|
+
] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StrictLimit(BaseModel):
|
|
29
|
+
limit: Annotated[Limit, Field(Limit.LIM_10, description="Page limit")] = (
|
|
30
|
+
Limit.LIM_10
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PageInfo(BaseModel):
|
|
35
|
+
data_count: Annotated[int, Field(0, description="Fetched data count", ge=0)] = 0
|
|
36
|
+
total_data: Annotated[int, Field(0, description="Total data count", ge=0)] = 0
|
|
37
|
+
total_pages: Annotated[int, Field(1, description="Total pages count", ge=1)] = 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BaseFlexiblePagination(FlexibleLimit, Page):
|
|
41
|
+
@model_validator(mode="after")
|
|
42
|
+
def validate_page_and_limit(self) -> Self:
|
|
43
|
+
if self.limit is None:
|
|
44
|
+
self.page = 1
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FlexiblePagination(PageInfo, BaseFlexiblePagination):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class BaseStrictPagination(StrictLimit, Page):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class StrictPagination(PageInfo, BaseStrictPagination):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
AnyPagination = FlexiblePagination | StrictPagination
|
|
61
|
+
PaginationT = TypeVar("PaginationT", bound=AnyPagination)
|
|
62
|
+
OptAnyPagination = AnyPagination | None
|
|
63
|
+
OptPaginationT = TypeVar("OptPaginationT", bound=OptAnyPagination)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class PaginationMixin(BaseModel, Generic[OptPaginationT]):
|
|
67
|
+
pagination: Annotated[OptPaginationT, Field(..., description="Pagination")]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Generic
|
|
2
|
+
from .mixins.filter import DateFilters
|
|
3
|
+
from .mixins.identity import IdentifierMixin, IdentifierT
|
|
4
|
+
from .mixins.parameter import (
|
|
5
|
+
MandatorySimpleDataStatusesMixin,
|
|
6
|
+
Search,
|
|
7
|
+
UseCache,
|
|
8
|
+
)
|
|
9
|
+
from .mixins.sort import SortColumns
|
|
10
|
+
from .operation.action.status import StatusUpdateOperationAction
|
|
11
|
+
from .pagination import BaseFlexiblePagination, BaseStrictPagination
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReadSingleParameter(
|
|
15
|
+
UseCache,
|
|
16
|
+
MandatorySimpleDataStatusesMixin,
|
|
17
|
+
IdentifierMixin[IdentifierT],
|
|
18
|
+
Generic[IdentifierT],
|
|
19
|
+
):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseReadMultipleParameter(
|
|
24
|
+
SortColumns,
|
|
25
|
+
Search,
|
|
26
|
+
MandatorySimpleDataStatusesMixin,
|
|
27
|
+
DateFilters,
|
|
28
|
+
):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ReadUnpaginatedMultipleParameter(
|
|
33
|
+
UseCache,
|
|
34
|
+
BaseFlexiblePagination,
|
|
35
|
+
BaseReadMultipleParameter,
|
|
36
|
+
):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ReadPaginatedMultipleParameter(
|
|
41
|
+
UseCache,
|
|
42
|
+
BaseStrictPagination,
|
|
43
|
+
BaseReadMultipleParameter,
|
|
44
|
+
):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class StatusUpdateParameter(
|
|
49
|
+
StatusUpdateOperationAction,
|
|
50
|
+
IdentifierMixin[IdentifierT],
|
|
51
|
+
Generic[IdentifierT],
|
|
52
|
+
):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DeleteSingleParameter(
|
|
57
|
+
IdentifierMixin[IdentifierT],
|
|
58
|
+
Generic[IdentifierT],
|
|
59
|
+
):
|
|
60
|
+
pass
|
nexo/schemas/payload.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import Generic, TypeVar
|
|
3
|
+
from .data import DataPair, AnyDataT, DataMixin, ModelDataT
|
|
4
|
+
from .metadata import MetadataMixin, AnyMetadataT, ModelMetadataT
|
|
5
|
+
from .mixins.general import Other
|
|
6
|
+
from .pagination import OptPaginationT, PaginationT, PaginationMixin
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Payload(
|
|
10
|
+
Other,
|
|
11
|
+
MetadataMixin[AnyMetadataT],
|
|
12
|
+
PaginationMixin[OptPaginationT],
|
|
13
|
+
DataMixin[AnyDataT],
|
|
14
|
+
BaseModel,
|
|
15
|
+
Generic[AnyDataT, OptPaginationT, AnyMetadataT],
|
|
16
|
+
):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
PayloadT = TypeVar("PayloadT", bound=Payload)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PayloadMixin(BaseModel, Generic[PayloadT]):
|
|
24
|
+
payload: PayloadT = Field(..., description="Payload")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NoDataPayload(
|
|
28
|
+
Payload[None, None, ModelMetadataT],
|
|
29
|
+
Generic[ModelMetadataT],
|
|
30
|
+
):
|
|
31
|
+
data: None = None
|
|
32
|
+
pagination: None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SingleDataPayload(
|
|
36
|
+
Payload[ModelDataT, None, ModelMetadataT],
|
|
37
|
+
Generic[ModelDataT, ModelMetadataT],
|
|
38
|
+
):
|
|
39
|
+
pagination: None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CreateSingleDataPayload(
|
|
43
|
+
Payload[DataPair[None, ModelDataT], None, ModelMetadataT],
|
|
44
|
+
Generic[ModelDataT, ModelMetadataT],
|
|
45
|
+
):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ReadSingleDataPayload(
|
|
50
|
+
Payload[DataPair[ModelDataT, None], None, ModelMetadataT],
|
|
51
|
+
Generic[ModelDataT, ModelMetadataT],
|
|
52
|
+
):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UpdateSingleDataPayload(
|
|
57
|
+
Payload[DataPair[ModelDataT, ModelDataT], None, ModelMetadataT],
|
|
58
|
+
Generic[ModelDataT, ModelMetadataT],
|
|
59
|
+
):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DeleteSingleDataPayload(
|
|
64
|
+
Payload[DataPair[ModelDataT, None], None, ModelMetadataT],
|
|
65
|
+
Generic[ModelDataT, ModelMetadataT],
|
|
66
|
+
):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class OptSingleDataPayload(
|
|
71
|
+
Payload[ModelDataT | None, None, ModelMetadataT],
|
|
72
|
+
Generic[ModelDataT, ModelMetadataT],
|
|
73
|
+
):
|
|
74
|
+
pagination: None = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class MultipleDataPayload(
|
|
78
|
+
Payload[list[ModelDataT], PaginationT, ModelMetadataT],
|
|
79
|
+
Generic[ModelDataT, PaginationT, ModelMetadataT],
|
|
80
|
+
):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class CreateMultipleDataPayload(
|
|
85
|
+
Payload[DataPair[None, list[ModelDataT]], PaginationT, ModelMetadataT],
|
|
86
|
+
Generic[ModelDataT, PaginationT, ModelMetadataT],
|
|
87
|
+
):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ReadMultipleDataPayload(
|
|
92
|
+
Payload[DataPair[list[ModelDataT], None], PaginationT, ModelMetadataT],
|
|
93
|
+
Generic[ModelDataT, PaginationT, ModelMetadataT],
|
|
94
|
+
):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class UpdateMultipleDataPayload(
|
|
99
|
+
Payload[DataPair[list[ModelDataT], list[ModelDataT]], PaginationT, ModelMetadataT],
|
|
100
|
+
Generic[ModelDataT, PaginationT, ModelMetadataT],
|
|
101
|
+
):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class DeleteMultipleDataPayload(
|
|
106
|
+
Payload[DataPair[list[ModelDataT], None], PaginationT, ModelMetadataT],
|
|
107
|
+
Generic[ModelDataT, PaginationT, ModelMetadataT],
|
|
108
|
+
):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class OptMultipleDataPayload(
|
|
113
|
+
Payload[list[ModelDataT] | None, PaginationT, ModelMetadataT],
|
|
114
|
+
Generic[ModelDataT, PaginationT, ModelMetadataT],
|
|
115
|
+
):
|
|
116
|
+
pass
|
nexo/schemas/resource.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from typing import Annotated, Generic, Literal, TypeVar, overload
|
|
4
|
+
from nexo.types.dict import OptStrToAnyDict
|
|
5
|
+
from nexo.types.string import ListOfStrs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AggregateField(StrEnum):
|
|
9
|
+
KEY = "key"
|
|
10
|
+
NAME = "name"
|
|
11
|
+
SLUG = "slug"
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def choices(cls) -> ListOfStrs:
|
|
15
|
+
return [e.value for e in cls]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ResourceIdentifier(BaseModel):
|
|
19
|
+
key: Annotated[str, Field(..., description="Key", pattern=r"^[a-zA-Z0-9_-]+$")]
|
|
20
|
+
name: Annotated[str, Field(..., description="Name")]
|
|
21
|
+
slug: Annotated[
|
|
22
|
+
str, Field(..., description="URL Slug", pattern=r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Resource(BaseModel):
|
|
27
|
+
identifiers: Annotated[
|
|
28
|
+
list[ResourceIdentifier], Field(..., min_length=1, description="Identifiers")
|
|
29
|
+
]
|
|
30
|
+
details: Annotated[OptStrToAnyDict, Field(None, description="Details")] = None
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def aggregate(
|
|
34
|
+
self, field: Literal[AggregateField.KEY], *, sep: str = "_"
|
|
35
|
+
) -> str: ...
|
|
36
|
+
@overload
|
|
37
|
+
def aggregate(
|
|
38
|
+
self, field: Literal[AggregateField.NAME], *, sep: str = " "
|
|
39
|
+
) -> str: ...
|
|
40
|
+
@overload
|
|
41
|
+
def aggregate(
|
|
42
|
+
self, field: Literal[AggregateField.SLUG], *, sep: str = "/"
|
|
43
|
+
) -> str: ...
|
|
44
|
+
@overload
|
|
45
|
+
def aggregate(
|
|
46
|
+
self, field: AggregateField = AggregateField.KEY, *, sep: str = "_"
|
|
47
|
+
) -> str: ...
|
|
48
|
+
def aggregate(
|
|
49
|
+
self, field: AggregateField = AggregateField.KEY, *, sep: str = "_"
|
|
50
|
+
) -> str:
|
|
51
|
+
if field is AggregateField.KEY:
|
|
52
|
+
return sep.join([id.key for id in self.identifiers])
|
|
53
|
+
elif field is AggregateField.NAME:
|
|
54
|
+
return sep.join([id.name for id in self.identifiers])
|
|
55
|
+
elif field is AggregateField.SLUG:
|
|
56
|
+
return sep.join([id.slug for id in self.identifiers])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
OptResource = Resource | None
|
|
60
|
+
OptResourceT = TypeVar("OptResourceT", bound=OptResource)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ResourceMixin(BaseModel, Generic[OptResourceT]):
|
|
64
|
+
resource: OptResourceT = Field(..., description="Resource")
|