maleo-foundation 0.3.71__py3-none-any.whl → 0.3.74__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.
- maleo_foundation/authentication.py +2 -48
- maleo_foundation/client/manager.py +9 -3
- maleo_foundation/constants.py +2 -0
- maleo_foundation/controller_types.py +25 -0
- maleo_foundation/enums.py +7 -1
- maleo_foundation/managers/client/base.py +37 -5
- maleo_foundation/managers/client/google/base.py +7 -3
- maleo_foundation/managers/client/google/secret.py +12 -3
- maleo_foundation/managers/client/google/storage.py +11 -2
- maleo_foundation/managers/client/google/subscription.py +40 -26
- maleo_foundation/managers/client/maleo.py +3 -5
- maleo_foundation/managers/credential.py +6 -2
- maleo_foundation/managers/middleware.py +8 -8
- maleo_foundation/managers/service.py +33 -17
- maleo_foundation/middlewares/authentication.py +3 -2
- maleo_foundation/middlewares/base.py +312 -197
- maleo_foundation/models/schemas/general.py +1 -127
- maleo_foundation/models/transfers/general/authentication.py +35 -0
- maleo_foundation/{authorization.py → models/transfers/general/authorization.py} +0 -3
- maleo_foundation/models/transfers/general/configurations/__init__.py +2 -0
- maleo_foundation/models/transfers/general/configurations/client/maleo.py +1 -1
- maleo_foundation/models/transfers/general/configurations/middleware.py +6 -7
- maleo_foundation/models/transfers/general/configurations/pubsub/subscription.py +16 -0
- maleo_foundation/models/transfers/general/configurations/service.py +2 -1
- maleo_foundation/models/transfers/general/operation.py +192 -30
- maleo_foundation/models/transfers/general/request.py +13 -19
- maleo_foundation/models/transfers/general/response.py +14 -0
- maleo_foundation/models/transfers/general/service.py +9 -0
- maleo_foundation/models/transfers/general/settings.py +1 -1
- maleo_foundation/models/transfers/general/user_agent.py +34 -0
- maleo_foundation/utils/exceptions/client.py +26 -2
- maleo_foundation/utils/exceptions/service.py +26 -2
- maleo_foundation/utils/extractor.py +49 -19
- maleo_foundation/utils/logging.py +90 -21
- maleo_foundation/utils/parser.py +7 -0
- {maleo_foundation-0.3.71.dist-info → maleo_foundation-0.3.74.dist-info}/METADATA +3 -1
- {maleo_foundation-0.3.71.dist-info → maleo_foundation-0.3.74.dist-info}/RECORD +39 -35
- maleo_foundation/utils/dependencies/__init__.py +0 -6
- maleo_foundation/utils/dependencies/auth.py +0 -17
- maleo_foundation/utils/dependencies/context.py +0 -8
- {maleo_foundation-0.3.71.dist-info → maleo_foundation-0.3.74.dist-info}/WHEEL +0 -0
- {maleo_foundation-0.3.71.dist-info → maleo_foundation-0.3.74.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from datetime import datetime, timezone
|
3
|
-
from pydantic import BaseModel, Field
|
4
|
-
from typing import Any, Optional, Self
|
3
|
+
from pydantic import BaseModel, Field
|
5
4
|
from uuid import UUID
|
6
5
|
from maleo_foundation.enums import BaseEnums
|
7
6
|
from maleo_foundation.types import BaseTypes
|
@@ -23,131 +22,6 @@ class BaseGeneralSchemas:
|
|
23
22
|
10, ge=1, le=100, description="Page size, must be 1 <= limit <= 100."
|
24
23
|
)
|
25
24
|
|
26
|
-
# * ----- ----- ----- Operation ----- ----- ----- *#
|
27
|
-
class OperationArguments(BaseModel):
|
28
|
-
positional: BaseTypes.ListOfAny = Field([], description="Positional arguments")
|
29
|
-
keyword: BaseTypes.StringToAnyDict = Field({}, description="Keyword arguments")
|
30
|
-
|
31
|
-
class OperationContext(BaseModel):
|
32
|
-
origin: BaseEnums.OperationOrigin = Field(..., description="Operation's origin")
|
33
|
-
client_key: BaseTypes.OptionalString = Field(None, description="Client's key")
|
34
|
-
layer: BaseEnums.OperationLayer = Field(..., description="Operation's layer")
|
35
|
-
target: Optional[BaseEnums.OperationTarget] = Field(
|
36
|
-
None, description="Operation's target (optional)"
|
37
|
-
)
|
38
|
-
target_environment: Optional[BaseEnums.EnvironmentType] = Field(
|
39
|
-
None, description="Operation's target's environment (optional)"
|
40
|
-
)
|
41
|
-
target_name: BaseTypes.OptionalString = Field(
|
42
|
-
None, description="Operation's target's name (optional)"
|
43
|
-
)
|
44
|
-
target_resource: BaseTypes.OptionalString = Field(
|
45
|
-
None, description="Operation's target's resource (optional)"
|
46
|
-
)
|
47
|
-
|
48
|
-
class OperationMetadata(BaseModel):
|
49
|
-
type: BaseEnums.OperationType = Field(..., description="Operation's type")
|
50
|
-
create_type: Optional[BaseEnums.CreateType] = Field(
|
51
|
-
None, description="Create type (optional)"
|
52
|
-
)
|
53
|
-
update_type: Optional[BaseEnums.UpdateType] = Field(
|
54
|
-
None, description="Update type (optional)"
|
55
|
-
)
|
56
|
-
status_update_type: Optional[BaseEnums.StatusUpdateType] = Field(
|
57
|
-
None, description="Status update type (optional)"
|
58
|
-
)
|
59
|
-
summary: BaseTypes.OptionalString = Field(
|
60
|
-
None, description="Summary (optional)"
|
61
|
-
)
|
62
|
-
|
63
|
-
@model_validator(mode="after")
|
64
|
-
def validate_operation_types(self) -> Self:
|
65
|
-
# Validate create operation type
|
66
|
-
if self.type is BaseEnums.OperationType.CREATE:
|
67
|
-
if self.create_type is None:
|
68
|
-
raise ValueError(
|
69
|
-
"'create_type' must have value if 'type' is 'create'"
|
70
|
-
)
|
71
|
-
if self.create_type not in BaseEnums.CreateType:
|
72
|
-
raise ValueError(
|
73
|
-
f"'create_type' must be one of {[e.value for e in BaseEnums.CreateType]}"
|
74
|
-
)
|
75
|
-
|
76
|
-
# Validate update operation type
|
77
|
-
if self.type is BaseEnums.OperationType.UPDATE:
|
78
|
-
if self.update_type is None:
|
79
|
-
raise ValueError(
|
80
|
-
"'update_type' must have value if 'type' is 'update'"
|
81
|
-
)
|
82
|
-
if self.update_type not in BaseEnums.UpdateType:
|
83
|
-
raise ValueError(
|
84
|
-
f"'update_type' must be one of {[e.value for e in BaseEnums.UpdateType]}"
|
85
|
-
)
|
86
|
-
if self.update_type is BaseEnums.UpdateType.STATUS:
|
87
|
-
if self.status_update_type is None:
|
88
|
-
raise ValueError(
|
89
|
-
"'status_update_type' must have value if 'update_type' is 'status'"
|
90
|
-
)
|
91
|
-
if self.status_update_type not in BaseEnums.StatusUpdateType:
|
92
|
-
raise ValueError(
|
93
|
-
f"'status_update_type' must be one of {[e.value for e in BaseEnums.StatusUpdateType]}"
|
94
|
-
)
|
95
|
-
|
96
|
-
return self
|
97
|
-
|
98
|
-
class OperationServiceContext(BaseModel):
|
99
|
-
key: BaseEnums.Service = Field(..., description="Service's key")
|
100
|
-
environment: BaseEnums.EnvironmentType = Field(
|
101
|
-
..., description="Service's environment"
|
102
|
-
)
|
103
|
-
|
104
|
-
class OperationTimestamps(BaseModel):
|
105
|
-
started_at: BaseTypes.OptionalDatetime = Field(
|
106
|
-
None, description="Started at timestamp (Optional)"
|
107
|
-
)
|
108
|
-
finished_at: BaseTypes.OptionalDatetime = Field(
|
109
|
-
None, description="Finished at timestamp (Optional)"
|
110
|
-
)
|
111
|
-
duration: float = Field(0, description="Operation duration")
|
112
|
-
|
113
|
-
@model_validator(mode="after")
|
114
|
-
def calculate_duration(self) -> Self:
|
115
|
-
if self.started_at is not None and self.finished_at is not None:
|
116
|
-
self.duration = (self.finished_at - self.started_at).total_seconds()
|
117
|
-
|
118
|
-
return self
|
119
|
-
|
120
|
-
class OperationResult(BaseModel):
|
121
|
-
success: bool = Field(..., description="Success status")
|
122
|
-
code: BaseTypes.OptionalString = Field(None, description="Optional result code")
|
123
|
-
message: BaseTypes.OptionalString = Field(None, description="Optional message")
|
124
|
-
description: BaseTypes.OptionalString = Field(
|
125
|
-
None, description="Optional description"
|
126
|
-
)
|
127
|
-
data: Any = Field(..., description="Data")
|
128
|
-
metadata: BaseTypes.OptionalAny = Field(None, description="Optional metadata")
|
129
|
-
other: BaseTypes.OptionalAny = Field(
|
130
|
-
None, description="Optional other information"
|
131
|
-
)
|
132
|
-
|
133
|
-
class DatabaseOperationContext(BaseModel):
|
134
|
-
database: str = Field(..., description="Database name")
|
135
|
-
environment: BaseEnums.EnvironmentType = Field(
|
136
|
-
..., description="Database environment"
|
137
|
-
)
|
138
|
-
table: str = Field(..., description="Table name")
|
139
|
-
|
140
|
-
class DatabaseOperationResult(BaseModel):
|
141
|
-
data_id: int = Field(..., ge=1, description="Data's ID")
|
142
|
-
old_data: BaseTypes.OptionalAny = Field(None, description="Old data")
|
143
|
-
new_data: BaseTypes.OptionalAny = Field(None, description="New data")
|
144
|
-
|
145
|
-
@model_validator(mode="after")
|
146
|
-
def validate_data(self) -> Self:
|
147
|
-
if self.old_data is None and self.new_data is None:
|
148
|
-
raise ValueError("Either 'old_data' or 'new_data' must have value")
|
149
|
-
return self
|
150
|
-
|
151
25
|
# * ----- ----- ----- Data ----- ----- ----- *#
|
152
26
|
class Identifiers(BaseModel):
|
153
27
|
id: int = Field(..., ge=1, description="Data's ID, must be >= 1.")
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
from typing import Optional, Sequence
|
3
|
+
from maleo_foundation.enums import BaseEnums
|
4
|
+
from .token import (
|
5
|
+
MaleoFoundationTokenGeneralTransfers,
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
class Token(BaseModel):
|
10
|
+
type: BaseEnums.TokenType = Field(..., description="Token's type")
|
11
|
+
payload: MaleoFoundationTokenGeneralTransfers.DecodePayload = Field(
|
12
|
+
..., description="Token's payload"
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
class Credentials(BaseModel):
|
17
|
+
token: Optional[Token] = Field(None, description="Token")
|
18
|
+
scopes: Optional[Sequence[str]] = Field(None, description="Scopes")
|
19
|
+
|
20
|
+
|
21
|
+
class User(BaseModel):
|
22
|
+
is_authenticated: bool = Field(False, description="Authenticated")
|
23
|
+
display_name: str = Field("", description="Username")
|
24
|
+
identity: str = Field("", description="Email")
|
25
|
+
|
26
|
+
|
27
|
+
class Authentication(BaseModel):
|
28
|
+
credentials: Credentials = Field(
|
29
|
+
default_factory=Credentials, # type: ignore
|
30
|
+
description="Credential",
|
31
|
+
)
|
32
|
+
user: User = Field(
|
33
|
+
default_factory=User, # type: ignore
|
34
|
+
description="User",
|
35
|
+
)
|
@@ -5,6 +5,7 @@ from maleo_foundation.utils.logging import (
|
|
5
5
|
DatabaseLogger,
|
6
6
|
MiddlewareLogger,
|
7
7
|
RepositoryLogger,
|
8
|
+
RouterLogger,
|
8
9
|
ServiceLogger,
|
9
10
|
)
|
10
11
|
from .cache import CacheConfigurations
|
@@ -38,4 +39,5 @@ class Loggers(BaseModel):
|
|
38
39
|
database: DatabaseLogger = Field(..., description="Database logger")
|
39
40
|
middleware: MiddlewareLogger = Field(..., description="Middleware logger")
|
40
41
|
repository: RepositoryLogger = Field(..., description="Repository logger")
|
42
|
+
router: RouterLogger = Field(..., description="Router logger")
|
41
43
|
service: ServiceLogger = Field(..., description="Service logger")
|
@@ -7,7 +7,7 @@ class MaleoClientConfigurations(BaseModel):
|
|
7
7
|
environment: BaseEnums.EnvironmentType = Field(
|
8
8
|
..., description="Client's environment"
|
9
9
|
)
|
10
|
-
key:
|
10
|
+
key: BaseEnums.Service = Field(..., description="Client's key")
|
11
11
|
name: str = Field(..., description="Client's name")
|
12
12
|
url: str = Field(..., description="Client's URL")
|
13
13
|
|
@@ -2,16 +2,21 @@ from pydantic import BaseModel, Field
|
|
2
2
|
from typing import List
|
3
3
|
|
4
4
|
_ALLOW_METHODS: List[str] = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
5
|
+
|
5
6
|
_ALLOW_HEADERS: List[str] = [
|
6
7
|
"Authorization",
|
7
8
|
"Content-Type",
|
9
|
+
"X-Operation-Id",
|
8
10
|
"X-Request-Id",
|
9
11
|
"X-Requested-At",
|
10
12
|
"X-Signature",
|
11
13
|
]
|
14
|
+
|
15
|
+
|
12
16
|
_EXPOSE_HEADERS: List[str] = [
|
13
17
|
"X-New-Authorization",
|
14
18
|
"X-Process-Time",
|
19
|
+
"X-Operation-Id",
|
15
20
|
"X-Request-Id",
|
16
21
|
"X-Requested-At",
|
17
22
|
"X-Responded-At",
|
@@ -19,16 +24,13 @@ _EXPOSE_HEADERS: List[str] = [
|
|
19
24
|
]
|
20
25
|
|
21
26
|
|
22
|
-
class
|
27
|
+
class CORSMiddlewareConfigurations(BaseModel):
|
23
28
|
allow_origins: List[str] = Field(
|
24
29
|
default_factory=list, description="Allowed origins"
|
25
30
|
)
|
26
31
|
allow_methods: List[str] = Field(_ALLOW_METHODS, description="Allowed methods")
|
27
32
|
allow_headers: list[str] = Field(_ALLOW_HEADERS, description="Allowed headers")
|
28
33
|
allow_credentials: bool = Field(True, description="Allowed credentials")
|
29
|
-
|
30
|
-
|
31
|
-
class CORSMiddlewareConfigurations(BaseModel):
|
32
34
|
expose_headers: List[str] = Field(_EXPOSE_HEADERS, description="Exposed headers")
|
33
35
|
|
34
36
|
|
@@ -42,9 +44,6 @@ class BaseMiddlewareConfigurations(BaseModel):
|
|
42
44
|
|
43
45
|
|
44
46
|
class MiddlewareConfigurations(BaseModel):
|
45
|
-
general: GeneralMiddlewareConfigurations = Field(
|
46
|
-
..., description="Middleware's general configurations"
|
47
|
-
)
|
48
47
|
cors: CORSMiddlewareConfigurations = Field(
|
49
48
|
..., description="CORS middleware's configurations"
|
50
49
|
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field
|
2
|
+
from maleo_foundation.controller_types import ControllerTypes
|
3
|
+
|
4
|
+
|
5
|
+
class SubscriptionConfigurations(BaseModel):
|
6
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
7
|
+
|
8
|
+
id: str = Field(..., description="Subscription's ID")
|
9
|
+
max_messages: int = Field(10, description="Subscription's Max messages")
|
10
|
+
ack_deadline: int = Field(10, description="Subscription's ACK deadline")
|
11
|
+
|
12
|
+
|
13
|
+
class ExtendedSubscriptionConfigurations(SubscriptionConfigurations):
|
14
|
+
controller: ControllerTypes.OptionalMessageController = Field(
|
15
|
+
None, description="Optional message controller"
|
16
|
+
)
|
@@ -1,8 +1,9 @@
|
|
1
1
|
from pydantic import BaseModel, Field
|
2
|
+
from maleo_foundation.enums import BaseEnums
|
2
3
|
|
3
4
|
|
4
5
|
class ServiceConfigurations(BaseModel):
|
5
|
-
key:
|
6
|
+
key: BaseEnums.Service = Field(..., description="Service's key")
|
6
7
|
name: str = Field(..., description="Service's name")
|
7
8
|
host: str = Field(..., description="Service's host")
|
8
9
|
port: int = Field(..., description="Service's port")
|
@@ -1,51 +1,213 @@
|
|
1
|
-
|
2
|
-
from
|
1
|
+
import json
|
2
|
+
from pydantic import BaseModel, Field, model_validator
|
3
|
+
from typing import Any, Optional, Self
|
4
|
+
from uuid import UUID, uuid4
|
3
5
|
from .request import RequestContext
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from maleo_foundation.
|
6
|
+
from .response import ResponseContext
|
7
|
+
from .service import ServiceContext
|
8
|
+
from maleo_foundation.enums import BaseEnums
|
9
|
+
from maleo_foundation.models.transfers.general.authentication import Authentication
|
10
|
+
from maleo_foundation.models.transfers.general.authorization import Authorization
|
11
|
+
from maleo_foundation.types import BaseTypes
|
12
|
+
|
13
|
+
|
14
|
+
class OperationId(BaseModel):
|
15
|
+
operation_id: UUID = Field(uuid4(), description="Operation's ID")
|
16
|
+
|
17
|
+
|
18
|
+
class OperationArguments(BaseModel):
|
19
|
+
positional: BaseTypes.ListOfAny = Field([], description="Positional arguments")
|
20
|
+
keyword: BaseTypes.StringToAnyDict = Field({}, description="Keyword arguments")
|
21
|
+
|
22
|
+
|
23
|
+
class OperationOrigin(BaseModel):
|
24
|
+
type: BaseEnums.OperationOrigin = Field(
|
25
|
+
..., description="Operation's origin's type"
|
26
|
+
)
|
27
|
+
properties: BaseTypes.OptionalStringToStringDict = Field(
|
28
|
+
None, description="Operation's origin's properties"
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
class OperationLayer(BaseModel):
|
33
|
+
type: BaseEnums.OperationLayer = Field(..., description="Operation's layer's type")
|
34
|
+
properties: BaseTypes.OptionalStringToStringDict = Field(
|
35
|
+
None, description="Operation's layer's properties"
|
36
|
+
)
|
37
|
+
|
38
|
+
|
39
|
+
class OperationTarget(BaseModel):
|
40
|
+
type: BaseEnums.OperationTarget = Field(
|
41
|
+
..., description="Operation's target's type"
|
42
|
+
)
|
43
|
+
properties: BaseTypes.OptionalStringToStringDict = Field(
|
44
|
+
None, description="Operation's target's properties"
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
class OperationContext(BaseModel):
|
49
|
+
origin: OperationOrigin = Field(..., description="Operation's origin")
|
50
|
+
layer: OperationLayer = Field(..., description="Operation's layer")
|
51
|
+
target: Optional[OperationTarget] = Field(
|
52
|
+
None, description="Operation's target (optional)"
|
53
|
+
)
|
54
|
+
|
55
|
+
|
56
|
+
class OperationMetadata(BaseModel):
|
57
|
+
type: BaseEnums.OperationType = Field(..., description="Operation's type")
|
58
|
+
create_type: Optional[BaseEnums.CreateType] = Field(
|
59
|
+
None, description="Create type (optional)"
|
60
|
+
)
|
61
|
+
update_type: Optional[BaseEnums.UpdateType] = Field(
|
62
|
+
None, description="Update type (optional)"
|
63
|
+
)
|
64
|
+
status_update_type: Optional[BaseEnums.StatusUpdateType] = Field(
|
65
|
+
None, description="Status update type (optional)"
|
66
|
+
)
|
67
|
+
|
68
|
+
# @model_validator(mode="after")
|
69
|
+
# def validate_operation_types(self) -> Self:
|
70
|
+
# # Validate create operation type
|
71
|
+
# if self.type is BaseEnums.OperationType.CREATE:
|
72
|
+
# if self.create_type is None:
|
73
|
+
# raise ValueError(
|
74
|
+
# "'create_type' must have value if 'type' is 'create'"
|
75
|
+
# )
|
76
|
+
# if self.create_type not in BaseEnums.CreateType:
|
77
|
+
# raise ValueError(
|
78
|
+
# f"'create_type' must be one of {[e.value for e in BaseEnums.CreateType]}"
|
79
|
+
# )
|
80
|
+
|
81
|
+
# # Validate update operation type
|
82
|
+
# if self.type is BaseEnums.OperationType.UPDATE:
|
83
|
+
# if self.update_type is None:
|
84
|
+
# raise ValueError(
|
85
|
+
# "'update_type' must have value if 'type' is 'update'"
|
86
|
+
# )
|
87
|
+
# if self.update_type not in BaseEnums.UpdateType:
|
88
|
+
# raise ValueError(
|
89
|
+
# f"'update_type' must be one of {[e.value for e in BaseEnums.UpdateType]}"
|
90
|
+
# )
|
91
|
+
# if self.update_type is BaseEnums.UpdateType.STATUS:
|
92
|
+
# if self.status_update_type is None:
|
93
|
+
# raise ValueError(
|
94
|
+
# "'status_update_type' must have value if 'update_type' is 'status'"
|
95
|
+
# )
|
96
|
+
# if self.status_update_type not in BaseEnums.StatusUpdateType:
|
97
|
+
# raise ValueError(
|
98
|
+
# f"'status_update_type' must be one of {[e.value for e in BaseEnums.StatusUpdateType]}"
|
99
|
+
# )
|
100
|
+
|
101
|
+
# return self
|
102
|
+
|
103
|
+
|
104
|
+
class OperationException(BaseModel):
|
105
|
+
type: BaseEnums.ExceptionType = Field(
|
106
|
+
BaseEnums.ExceptionType.INTERNAL, description="Exception type"
|
107
|
+
)
|
108
|
+
raw: str = Field(..., description="Raw exception")
|
109
|
+
traceback: BaseTypes.ListOfStrings = Field(..., description="Traceback")
|
110
|
+
|
111
|
+
|
112
|
+
class OperationTimestamps(BaseModel):
|
113
|
+
started_at: BaseTypes.OptionalDatetime = Field(
|
114
|
+
None, description="Started at timestamp (Optional)"
|
115
|
+
)
|
116
|
+
finished_at: BaseTypes.OptionalDatetime = Field(
|
117
|
+
None, description="Finished at timestamp (Optional)"
|
118
|
+
)
|
119
|
+
duration: float = Field(0, description="Operation duration")
|
120
|
+
|
121
|
+
@model_validator(mode="after")
|
122
|
+
def calculate_duration(self) -> Self:
|
123
|
+
if self.started_at is not None and self.finished_at is not None:
|
124
|
+
self.duration = (self.finished_at - self.started_at).total_seconds()
|
125
|
+
|
126
|
+
return self
|
127
|
+
|
128
|
+
|
129
|
+
class OperationResult(BaseModel):
|
130
|
+
success: bool = Field(..., description="Success status")
|
131
|
+
code: BaseTypes.OptionalString = Field(None, description="Optional result code")
|
132
|
+
message: BaseTypes.OptionalString = Field(None, description="Optional message")
|
133
|
+
description: BaseTypes.OptionalString = Field(
|
134
|
+
None, description="Optional description"
|
135
|
+
)
|
136
|
+
data: Any = Field(..., description="Data")
|
137
|
+
metadata: BaseTypes.OptionalAny = Field(None, description="Optional metadata")
|
138
|
+
other: BaseTypes.OptionalAny = Field(None, description="Optional other information")
|
7
139
|
|
8
140
|
|
9
141
|
class Operation(BaseModel):
|
142
|
+
operation_id: UUID = Field(..., description="Operation's ID")
|
143
|
+
summary: str = Field(..., description="Operation's summary")
|
10
144
|
request_context: RequestContext = Field(..., description="Request context")
|
11
145
|
authentication: Authentication = Field(..., description="Authentication")
|
12
146
|
authorization: Optional[Authorization] = Field(None, description="Authorization")
|
13
|
-
service:
|
14
|
-
|
147
|
+
service: ServiceContext = Field(..., description="Service's context")
|
148
|
+
timestamps: OperationTimestamps = Field(..., description="Operation's timestamps")
|
149
|
+
context: OperationContext = Field(..., description="Operation's context")
|
150
|
+
metadata: OperationMetadata = Field(..., description="Operation's metadata")
|
151
|
+
exception: Optional[OperationException] = Field(
|
152
|
+
None, description="Operation's exception"
|
15
153
|
)
|
16
|
-
|
17
|
-
|
154
|
+
arguments: Optional[OperationArguments] = Field(
|
155
|
+
None, description="Operation's arguments"
|
18
156
|
)
|
19
|
-
|
20
|
-
|
157
|
+
result: Optional[OperationResult] = Field(None, description="Operation's result")
|
158
|
+
response_context: Optional[ResponseContext] = Field(
|
159
|
+
None, description="Response's context"
|
21
160
|
)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
161
|
+
|
162
|
+
def to_log_string(self, multiline: bool = True, indent: int = 2) -> str:
|
163
|
+
if not self.authentication.user.is_authenticated:
|
164
|
+
authentication = "Unauthenticated"
|
165
|
+
else:
|
166
|
+
authentication = (
|
167
|
+
"Authenticated | "
|
168
|
+
f"Username: {self.authentication.user.display_name} | "
|
169
|
+
f"Email: {self.authentication.user.identity}"
|
170
|
+
)
|
171
|
+
summary = f"Operation {self.operation_id} - {authentication} - {self.summary}"
|
172
|
+
payload = self.model_dump(mode="json")
|
173
|
+
if multiline:
|
174
|
+
details = f"\n{json.dumps(payload, indent=indent)}"
|
175
|
+
else:
|
176
|
+
details = f" - Details: {json.dumps(payload)}"
|
177
|
+
return f"{summary}{details}"
|
178
|
+
|
179
|
+
|
180
|
+
class DatabaseOperationContext(BaseModel):
|
181
|
+
database: str = Field(..., description="Database name")
|
182
|
+
environment: BaseEnums.EnvironmentType = Field(
|
183
|
+
..., description="Database environment"
|
30
184
|
)
|
185
|
+
table: str = Field(..., description="Table name")
|
186
|
+
|
187
|
+
|
188
|
+
class DatabaseOperationResult(BaseModel):
|
189
|
+
data_id: int = Field(..., ge=1, description="Data's ID")
|
190
|
+
old_data: BaseTypes.OptionalAny = Field(None, description="Old data")
|
191
|
+
new_data: BaseTypes.OptionalAny = Field(None, description="New data")
|
192
|
+
|
193
|
+
@model_validator(mode="after")
|
194
|
+
def validate_data(self) -> Self:
|
195
|
+
if self.old_data is None and self.new_data is None:
|
196
|
+
raise ValueError("Either 'old_data' or 'new_data' must have value")
|
197
|
+
return self
|
31
198
|
|
32
199
|
|
33
200
|
class DatabaseOperation(BaseModel):
|
201
|
+
operation_id: UUID = Field(uuid4(), description="Operation's ID")
|
34
202
|
request_context: RequestContext = Field(..., description="Request context")
|
35
203
|
authentication: Authentication = Field(..., description="Authentication")
|
36
204
|
authorization: Optional[Authorization] = Field(None, description="Authorization")
|
37
|
-
service:
|
38
|
-
|
39
|
-
|
40
|
-
timestamps: BaseGeneralSchemas.OperationTimestamps = Field(
|
41
|
-
..., description="Operation's timestamps"
|
42
|
-
)
|
43
|
-
context: BaseGeneralSchemas.DatabaseOperationContext = Field(
|
205
|
+
service: ServiceContext = Field(..., description="Service's context")
|
206
|
+
timestamps: OperationTimestamps = Field(..., description="Operation's timestamps")
|
207
|
+
context: DatabaseOperationContext = Field(
|
44
208
|
..., description="Database operation's context"
|
45
209
|
)
|
46
|
-
metadata:
|
47
|
-
|
48
|
-
)
|
49
|
-
result: BaseGeneralSchemas.DatabaseOperationResult = Field(
|
210
|
+
metadata: OperationMetadata = Field(..., description="Operation's metadata")
|
211
|
+
result: DatabaseOperationResult = Field(
|
50
212
|
..., description="Database operation's result"
|
51
213
|
)
|
@@ -1,39 +1,33 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from datetime import datetime
|
2
|
+
from datetime import datetime
|
3
3
|
from pydantic import BaseModel, ConfigDict, Field
|
4
|
+
from typing import List, Optional, Tuple
|
4
5
|
from uuid import UUID
|
5
6
|
from maleo_foundation.types import BaseTypes
|
7
|
+
from .user_agent import UserAgent
|
6
8
|
|
7
9
|
|
8
10
|
class RequestContext(BaseModel):
|
9
11
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
10
12
|
|
11
|
-
request_id: UUID = Field(
|
12
|
-
|
13
|
-
)
|
14
|
-
requested_at: datetime = Field(
|
15
|
-
datetime.now(tz=timezone.utc), description="Request timestamp"
|
16
|
-
)
|
13
|
+
request_id: UUID = Field(..., description="Request's ID")
|
14
|
+
requested_at: datetime = Field(..., description="Request timestamp")
|
17
15
|
method: str = Field(..., description="Request's method")
|
18
16
|
url: str = Field(..., description="Request's URL")
|
19
|
-
path_params: BaseTypes.OptionalStringToAnyDict = Field(
|
20
|
-
None, description="Request's path parameters"
|
21
|
-
)
|
22
|
-
query_params: BaseTypes.OptionalString = Field(
|
23
|
-
None, description="Request's query parameters"
|
24
|
-
)
|
25
17
|
ip_address: str = Field("unknown", description="Client's IP address")
|
26
18
|
is_internal: BaseTypes.OptionalBoolean = Field(
|
27
19
|
None, description="True if IP is internal"
|
28
20
|
)
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
headers: Optional[List[Tuple[str, str]]] = Field(
|
22
|
+
None, description="Request's headers"
|
23
|
+
)
|
24
|
+
path_params: BaseTypes.OptionalStringToStringDict = Field(
|
25
|
+
None, description="Request's path parameters"
|
32
26
|
)
|
33
|
-
|
34
|
-
|
35
|
-
None, description="Client platform or OS"
|
27
|
+
query_params: BaseTypes.OptionalString = Field(
|
28
|
+
None, description="Request's query parameters"
|
36
29
|
)
|
30
|
+
user_agent: UserAgent = Field(..., description="User agent")
|
37
31
|
referer: BaseTypes.OptionalString = Field(None, description="Referrer URL")
|
38
32
|
origin: BaseTypes.OptionalString = Field(None, description="Origin of the request")
|
39
33
|
host: BaseTypes.OptionalString = Field(None, description="Host header from request")
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from datetime import datetime
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
4
|
+
from typing import List, Optional, Tuple
|
5
|
+
|
6
|
+
|
7
|
+
class ResponseContext(BaseModel):
|
8
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
9
|
+
|
10
|
+
responded_at: datetime = Field(..., description="Respond timestamp")
|
11
|
+
process_time: float = Field(..., description="Process time")
|
12
|
+
headers: Optional[List[Tuple[str, str]]] = Field(
|
13
|
+
None, description="Response's headers"
|
14
|
+
)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
from maleo_foundation.enums import BaseEnums
|
3
|
+
|
4
|
+
|
5
|
+
class ServiceContext(BaseModel):
|
6
|
+
key: BaseEnums.Service = Field(..., description="Service's key")
|
7
|
+
environment: BaseEnums.EnvironmentType = Field(
|
8
|
+
..., description="Service's environment"
|
9
|
+
)
|
@@ -7,7 +7,7 @@ from maleo_foundation.types import BaseTypes
|
|
7
7
|
|
8
8
|
class Settings(BaseSettings):
|
9
9
|
ENVIRONMENT: BaseEnums.EnvironmentType = Field(..., description="Environment")
|
10
|
-
SERVICE_KEY:
|
10
|
+
SERVICE_KEY: BaseEnums.Service = Field(..., description="Service's key")
|
11
11
|
ROOT_PATH: str = Field("", description="Application's root path")
|
12
12
|
GOOGLE_CREDENTIALS_PATH: str = Field(
|
13
13
|
"/credentials/maleo-google-service-account.json",
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
from typing import Optional, Tuple
|
3
|
+
|
4
|
+
|
5
|
+
class Browser(BaseModel):
|
6
|
+
family: str
|
7
|
+
version: Tuple[int, ...]
|
8
|
+
version_string: str
|
9
|
+
|
10
|
+
|
11
|
+
class OperatingSystem(BaseModel):
|
12
|
+
family: str
|
13
|
+
version: Tuple[int, ...]
|
14
|
+
version_string: str
|
15
|
+
|
16
|
+
|
17
|
+
class Device(BaseModel):
|
18
|
+
family: str
|
19
|
+
brand: Optional[str] = None
|
20
|
+
model: Optional[str] = None
|
21
|
+
|
22
|
+
|
23
|
+
class UserAgent(BaseModel):
|
24
|
+
ua_string: str = Field(..., description="Raw User-Agent header")
|
25
|
+
browser: Browser = Field(..., description="Browser User-Agent")
|
26
|
+
os: OperatingSystem = Field(..., description="OS User-Agent")
|
27
|
+
device: Device = Field(..., description="Platform User-Agent")
|
28
|
+
|
29
|
+
is_mobile: bool
|
30
|
+
is_tablet: bool
|
31
|
+
is_pc: bool
|
32
|
+
is_bot: bool
|
33
|
+
is_touch_capable: bool
|
34
|
+
is_email_client: bool
|