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.
Files changed (42) hide show
  1. maleo_foundation/authentication.py +2 -48
  2. maleo_foundation/client/manager.py +9 -3
  3. maleo_foundation/constants.py +2 -0
  4. maleo_foundation/controller_types.py +25 -0
  5. maleo_foundation/enums.py +7 -1
  6. maleo_foundation/managers/client/base.py +37 -5
  7. maleo_foundation/managers/client/google/base.py +7 -3
  8. maleo_foundation/managers/client/google/secret.py +12 -3
  9. maleo_foundation/managers/client/google/storage.py +11 -2
  10. maleo_foundation/managers/client/google/subscription.py +40 -26
  11. maleo_foundation/managers/client/maleo.py +3 -5
  12. maleo_foundation/managers/credential.py +6 -2
  13. maleo_foundation/managers/middleware.py +8 -8
  14. maleo_foundation/managers/service.py +33 -17
  15. maleo_foundation/middlewares/authentication.py +3 -2
  16. maleo_foundation/middlewares/base.py +312 -197
  17. maleo_foundation/models/schemas/general.py +1 -127
  18. maleo_foundation/models/transfers/general/authentication.py +35 -0
  19. maleo_foundation/{authorization.py → models/transfers/general/authorization.py} +0 -3
  20. maleo_foundation/models/transfers/general/configurations/__init__.py +2 -0
  21. maleo_foundation/models/transfers/general/configurations/client/maleo.py +1 -1
  22. maleo_foundation/models/transfers/general/configurations/middleware.py +6 -7
  23. maleo_foundation/models/transfers/general/configurations/pubsub/subscription.py +16 -0
  24. maleo_foundation/models/transfers/general/configurations/service.py +2 -1
  25. maleo_foundation/models/transfers/general/operation.py +192 -30
  26. maleo_foundation/models/transfers/general/request.py +13 -19
  27. maleo_foundation/models/transfers/general/response.py +14 -0
  28. maleo_foundation/models/transfers/general/service.py +9 -0
  29. maleo_foundation/models/transfers/general/settings.py +1 -1
  30. maleo_foundation/models/transfers/general/user_agent.py +34 -0
  31. maleo_foundation/utils/exceptions/client.py +26 -2
  32. maleo_foundation/utils/exceptions/service.py +26 -2
  33. maleo_foundation/utils/extractor.py +49 -19
  34. maleo_foundation/utils/logging.py +90 -21
  35. maleo_foundation/utils/parser.py +7 -0
  36. {maleo_foundation-0.3.71.dist-info → maleo_foundation-0.3.74.dist-info}/METADATA +3 -1
  37. {maleo_foundation-0.3.71.dist-info → maleo_foundation-0.3.74.dist-info}/RECORD +39 -35
  38. maleo_foundation/utils/dependencies/__init__.py +0 -6
  39. maleo_foundation/utils/dependencies/auth.py +0 -17
  40. maleo_foundation/utils/dependencies/context.py +0 -8
  41. {maleo_foundation-0.3.71.dist-info → maleo_foundation-0.3.74.dist-info}/WHEEL +0 -0
  42. {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, model_validator
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
+ )
@@ -1,8 +1,5 @@
1
- from fastapi.security import HTTPBearer
2
1
  from pydantic import BaseModel, Field
3
2
 
4
- TOKEN_SCHEME = HTTPBearer()
5
-
6
3
 
7
4
  class Authorization(BaseModel):
8
5
  scheme: str = Field(..., description="Authorization's scheme")
@@ -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: str = Field(..., description="Client's 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 GeneralMiddlewareConfigurations(BaseModel):
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: str = Field(..., description="Service's 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
- from pydantic import BaseModel, Field
2
- from typing import Optional
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 maleo_foundation.authentication import Authentication
5
- from maleo_foundation.authorization import Authorization
6
- from maleo_foundation.models.schemas.general import BaseGeneralSchemas
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: BaseGeneralSchemas.OperationServiceContext = Field(
14
- ..., description="Service's context"
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
- timestamps: BaseGeneralSchemas.OperationTimestamps = Field(
17
- ..., description="Operation's timestamps"
154
+ arguments: Optional[OperationArguments] = Field(
155
+ None, description="Operation's arguments"
18
156
  )
19
- context: BaseGeneralSchemas.OperationContext = Field(
20
- ..., description="Operation's context"
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
- metadata: BaseGeneralSchemas.OperationMetadata = Field(
23
- ..., description="Operation's metadata"
24
- )
25
- arguments: BaseGeneralSchemas.OperationArguments = Field(
26
- ..., description="Operation's arguments"
27
- )
28
- result: BaseGeneralSchemas.OperationResult = Field(
29
- ..., description="Database operation's result"
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: BaseGeneralSchemas.OperationServiceContext = Field(
38
- ..., description="Service's context"
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: BaseGeneralSchemas.OperationMetadata = Field(
47
- ..., description="Operation's metadata"
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, timezone
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
- ..., description="Unique identifier for tracing the request"
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
- user_agent: BaseTypes.OptionalString = Field(None, description="User-Agent string")
30
- ua_browser: BaseTypes.OptionalString = Field(
31
- None, description="Browser info from sec-ch-ua"
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
- ua_mobile: BaseTypes.OptionalString = Field(None, description="Is mobile device?")
34
- platform: BaseTypes.OptionalString = Field(
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: str = Field(..., description="Service's 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