maleo-foundation 0.3.45__py3-none-any.whl → 0.3.47__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 (135) hide show
  1. maleo_foundation/authentication.py +24 -13
  2. maleo_foundation/authorization.py +2 -1
  3. maleo_foundation/client/manager.py +22 -21
  4. maleo_foundation/client/services/__init__.py +16 -7
  5. maleo_foundation/client/services/encryption/__init__.py +13 -4
  6. maleo_foundation/client/services/encryption/aes.py +41 -36
  7. maleo_foundation/client/services/encryption/rsa.py +50 -50
  8. maleo_foundation/client/services/hash/__init__.py +19 -6
  9. maleo_foundation/client/services/hash/bcrypt.py +20 -18
  10. maleo_foundation/client/services/hash/hmac.py +20 -17
  11. maleo_foundation/client/services/hash/sha256.py +18 -15
  12. maleo_foundation/client/services/key.py +50 -42
  13. maleo_foundation/client/services/signature.py +46 -42
  14. maleo_foundation/client/services/token.py +49 -58
  15. maleo_foundation/constants.py +13 -14
  16. maleo_foundation/enums.py +14 -13
  17. maleo_foundation/expanded_types/__init__.py +2 -3
  18. maleo_foundation/expanded_types/client.py +30 -34
  19. maleo_foundation/expanded_types/encryption/__init__.py +2 -1
  20. maleo_foundation/expanded_types/encryption/aes.py +7 -5
  21. maleo_foundation/expanded_types/encryption/rsa.py +7 -5
  22. maleo_foundation/expanded_types/general.py +13 -11
  23. maleo_foundation/expanded_types/hash.py +7 -5
  24. maleo_foundation/expanded_types/key.py +8 -6
  25. maleo_foundation/expanded_types/service.py +30 -34
  26. maleo_foundation/expanded_types/signature.py +7 -5
  27. maleo_foundation/expanded_types/token.py +7 -5
  28. maleo_foundation/extended_types.py +4 -3
  29. maleo_foundation/managers/cache.py +2 -1
  30. maleo_foundation/managers/client/base.py +25 -12
  31. maleo_foundation/managers/client/google/base.py +11 -4
  32. maleo_foundation/managers/client/google/parameter.py +9 -11
  33. maleo_foundation/managers/client/google/secret.py +53 -35
  34. maleo_foundation/managers/client/google/storage.py +52 -22
  35. maleo_foundation/managers/client/google/subscription.py +37 -39
  36. maleo_foundation/managers/client/maleo.py +18 -23
  37. maleo_foundation/managers/configuration.py +5 -9
  38. maleo_foundation/managers/credential.py +14 -17
  39. maleo_foundation/managers/db.py +51 -40
  40. maleo_foundation/managers/middleware.py +9 -9
  41. maleo_foundation/managers/service.py +47 -54
  42. maleo_foundation/middlewares/authentication.py +29 -54
  43. maleo_foundation/middlewares/base.py +83 -72
  44. maleo_foundation/middlewares/cors.py +8 -7
  45. maleo_foundation/models/__init__.py +2 -1
  46. maleo_foundation/models/responses.py +57 -29
  47. maleo_foundation/models/schemas/__init__.py +2 -1
  48. maleo_foundation/models/schemas/encryption.py +5 -2
  49. maleo_foundation/models/schemas/general.py +38 -18
  50. maleo_foundation/models/schemas/hash.py +2 -1
  51. maleo_foundation/models/schemas/key.py +5 -2
  52. maleo_foundation/models/schemas/parameter.py +45 -15
  53. maleo_foundation/models/schemas/result.py +35 -20
  54. maleo_foundation/models/schemas/signature.py +5 -2
  55. maleo_foundation/models/schemas/token.py +5 -2
  56. maleo_foundation/models/table.py +33 -27
  57. maleo_foundation/models/transfers/__init__.py +2 -1
  58. maleo_foundation/models/transfers/general/__init__.py +2 -1
  59. maleo_foundation/models/transfers/general/configurations/__init__.py +10 -4
  60. maleo_foundation/models/transfers/general/configurations/cache/__init__.py +3 -2
  61. maleo_foundation/models/transfers/general/configurations/cache/redis.py +13 -5
  62. maleo_foundation/models/transfers/general/configurations/client/__init__.py +5 -1
  63. maleo_foundation/models/transfers/general/configurations/client/maleo.py +38 -12
  64. maleo_foundation/models/transfers/general/configurations/database.py +5 -2
  65. maleo_foundation/models/transfers/general/configurations/middleware.py +22 -15
  66. maleo_foundation/models/transfers/general/configurations/service.py +2 -1
  67. maleo_foundation/models/transfers/general/credentials.py +2 -1
  68. maleo_foundation/models/transfers/general/database.py +11 -4
  69. maleo_foundation/models/transfers/general/key.py +13 -4
  70. maleo_foundation/models/transfers/general/request.py +28 -9
  71. maleo_foundation/models/transfers/general/settings.py +12 -22
  72. maleo_foundation/models/transfers/general/signature.py +4 -2
  73. maleo_foundation/models/transfers/general/token.py +34 -27
  74. maleo_foundation/models/transfers/parameters/__init__.py +2 -1
  75. maleo_foundation/models/transfers/parameters/client.py +15 -19
  76. maleo_foundation/models/transfers/parameters/encryption/__init__.py +2 -1
  77. maleo_foundation/models/transfers/parameters/encryption/aes.py +7 -5
  78. maleo_foundation/models/transfers/parameters/encryption/rsa.py +7 -5
  79. maleo_foundation/models/transfers/parameters/general.py +15 -13
  80. maleo_foundation/models/transfers/parameters/hash/__init__.py +2 -1
  81. maleo_foundation/models/transfers/parameters/hash/bcrypt.py +5 -5
  82. maleo_foundation/models/transfers/parameters/hash/hmac.py +6 -6
  83. maleo_foundation/models/transfers/parameters/hash/sha256.py +5 -5
  84. maleo_foundation/models/transfers/parameters/key.py +9 -8
  85. maleo_foundation/models/transfers/parameters/service.py +42 -48
  86. maleo_foundation/models/transfers/parameters/signature.py +7 -4
  87. maleo_foundation/models/transfers/parameters/token.py +10 -10
  88. maleo_foundation/models/transfers/results/__init__.py +2 -1
  89. maleo_foundation/models/transfers/results/client/__init__.py +2 -1
  90. maleo_foundation/models/transfers/results/client/controllers/__init__.py +2 -1
  91. maleo_foundation/models/transfers/results/client/controllers/http.py +10 -7
  92. maleo_foundation/models/transfers/results/client/service.py +12 -6
  93. maleo_foundation/models/transfers/results/encryption/__init__.py +2 -1
  94. maleo_foundation/models/transfers/results/encryption/aes.py +13 -5
  95. maleo_foundation/models/transfers/results/encryption/rsa.py +12 -4
  96. maleo_foundation/models/transfers/results/hash.py +7 -3
  97. maleo_foundation/models/transfers/results/key.py +18 -6
  98. maleo_foundation/models/transfers/results/service/__init__.py +2 -3
  99. maleo_foundation/models/transfers/results/service/controllers/__init__.py +2 -1
  100. maleo_foundation/models/transfers/results/service/controllers/rest.py +14 -11
  101. maleo_foundation/models/transfers/results/service/general.py +16 -10
  102. maleo_foundation/models/transfers/results/signature.py +12 -4
  103. maleo_foundation/models/transfers/results/token.py +10 -4
  104. maleo_foundation/rest_controller_result.py +23 -21
  105. maleo_foundation/types.py +15 -14
  106. maleo_foundation/utils/__init__.py +2 -1
  107. maleo_foundation/utils/cache.py +10 -13
  108. maleo_foundation/utils/client.py +25 -12
  109. maleo_foundation/utils/controller.py +59 -37
  110. maleo_foundation/utils/dependencies/__init__.py +2 -1
  111. maleo_foundation/utils/dependencies/auth.py +5 -12
  112. maleo_foundation/utils/dependencies/context.py +3 -4
  113. maleo_foundation/utils/exceptions.py +50 -28
  114. maleo_foundation/utils/extractor.py +18 -6
  115. maleo_foundation/utils/formatter/__init__.py +2 -1
  116. maleo_foundation/utils/formatter/case.py +5 -4
  117. maleo_foundation/utils/loaders/__init__.py +2 -1
  118. maleo_foundation/utils/loaders/credential/__init__.py +2 -1
  119. maleo_foundation/utils/loaders/credential/google.py +29 -15
  120. maleo_foundation/utils/loaders/json.py +3 -2
  121. maleo_foundation/utils/loaders/key/__init__.py +2 -1
  122. maleo_foundation/utils/loaders/key/rsa.py +26 -13
  123. maleo_foundation/utils/loaders/yaml.py +2 -1
  124. maleo_foundation/utils/logging.py +70 -46
  125. maleo_foundation/utils/merger.py +7 -9
  126. maleo_foundation/utils/query.py +41 -34
  127. maleo_foundation/utils/repository.py +28 -13
  128. maleo_foundation/utils/searcher.py +4 -6
  129. {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/METADATA +14 -1
  130. maleo_foundation-0.3.47.dist-info/RECORD +137 -0
  131. maleo_foundation/expanded_types/repository.py +0 -68
  132. maleo_foundation/models/transfers/results/service/repository.py +0 -39
  133. maleo_foundation-0.3.45.dist-info/RECORD +0 -139
  134. {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/WHEEL +0 -0
  135. {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/top_level.txt +0 -0
@@ -3,8 +3,10 @@ from functools import wraps
3
3
  from pydantic import ValidationError
4
4
  from typing import Optional, Type, Union
5
5
  from maleo_foundation.types import BaseTypes
6
- from maleo_foundation.models.transfers.results.client.service import \
7
- BaseClientServiceResultsTransfers
6
+ from maleo_foundation.models.transfers.results.client.service import (
7
+ BaseClientServiceResultsTransfers,
8
+ )
9
+
8
10
 
9
11
  class BaseClientUtils:
10
12
  @staticmethod
@@ -13,49 +15,60 @@ class BaseClientUtils:
13
15
  data_found_class: Union[
14
16
  Type[BaseClientServiceResultsTransfers.SingleData],
15
17
  Type[BaseClientServiceResultsTransfers.UnpaginatedMultipleData],
16
- Type[BaseClientServiceResultsTransfers.PaginatedMultipleData]
18
+ Type[BaseClientServiceResultsTransfers.PaginatedMultipleData],
17
19
  ],
18
20
  no_data_class: Optional[Type[BaseClientServiceResultsTransfers.NoData]] = None,
19
21
  ):
20
22
  """Decorator to handle repository-related exceptions consistently."""
23
+
21
24
  def decorator(func):
22
25
  def _processor(result: BaseTypes.StringToAnyDict):
23
26
  if "success" not in result and "data" not in result:
24
- raise ValueError("Result did not have both 'success' and 'data' field")
25
- success:bool = result.get("success")
26
- data:BaseTypes.StringToAnyDict = result.get("data")
27
+ raise ValueError(
28
+ "Result did not have both 'success' and 'data' field"
29
+ )
30
+ success: bool = result.get("success", False)
31
+ data: BaseTypes.StringToAnyDict = result.get("data", {})
27
32
  if not success:
28
33
  validated_result = fail_class.model_validate(result)
29
34
  return validated_result
30
35
  else:
31
36
  if data is None:
32
37
  if no_data_class is None:
33
- raise ValueError("'no_data_class' must be given to validate No Data")
38
+ raise ValueError(
39
+ "'no_data_class' must be given to validate No Data"
40
+ )
34
41
  validated_result = no_data_class.model_validate(result)
35
42
  return validated_result
36
43
  else:
37
44
  validated_result = data_found_class.model_validate(result)
38
45
  return validated_result
46
+
39
47
  if asyncio.iscoroutinefunction(func):
48
+
40
49
  @wraps(func)
41
50
  async def async_wrapper(*args, **kwargs):
42
51
  try:
43
52
  result: BaseTypes.StringToAnyDict = await func(*args, **kwargs)
44
53
  return _processor(result=result)
45
- except ValidationError as e:
54
+ except ValidationError:
46
55
  raise
47
- except Exception as e:
56
+ except Exception:
48
57
  raise
58
+
49
59
  return async_wrapper
50
60
  else:
61
+
51
62
  @wraps(func)
52
63
  def sync_wrapper(*args, **kwargs):
53
64
  try:
54
65
  result: BaseTypes.StringToAnyDict = func(*args, **kwargs)
55
66
  return _processor(result=result)
56
- except ValidationError as e:
67
+ except ValidationError:
57
68
  raise
58
- except Exception as e:
69
+ except Exception:
59
70
  raise
71
+
60
72
  return sync_wrapper
61
- return decorator
73
+
74
+ return decorator
@@ -1,28 +1,32 @@
1
1
  import inspect
2
2
  from fastapi import status
3
3
  from functools import wraps
4
- from typing import Awaitable, Callable, Dict, List, Union
4
+ from typing import Awaitable, Callable, Dict, List, cast
5
5
  from maleo_foundation.types import BaseTypes
6
6
  from maleo_foundation.models.responses import BaseResponses
7
- from maleo_foundation.models.transfers.parameters.general \
8
- import BaseGeneralParametersTransfers
9
- from maleo_foundation.models.transfers.results.service.controllers.rest \
10
- import BaseServiceRESTControllerResults
7
+ from maleo_foundation.models.transfers.parameters.general import (
8
+ BaseGeneralParametersTransfers,
9
+ )
10
+ from maleo_foundation.models.transfers.results.service.controllers.rest import (
11
+ BaseServiceRESTControllerResults,
12
+ )
11
13
  from maleo_foundation.expanded_types.general import BaseGeneralExpandedTypes
12
14
 
15
+
13
16
  class BaseControllerUtils:
14
17
  @staticmethod
15
18
  def field_expansion_handler(
16
19
  expandable_fields_dependencies_map: BaseTypes.OptionalStringToListOfStringDict = None,
17
- field_expansion_processors: BaseGeneralExpandedTypes.OptionalListOfFieldExpansionProcessor = None
20
+ field_expansion_processors: BaseGeneralExpandedTypes.OptionalListOfFieldExpansionProcessor = None,
18
21
  ):
19
22
  """
20
23
  Decorator to handle expandable fields validation and processing.
21
-
24
+
22
25
  Args:
23
26
  expandable_fields_dependencies_map: Dictionary where keys are dependency fields and values are lists of dependent fields
24
27
  field_expansion_processors: List of processor functions that handle that field's data
25
28
  """
29
+
26
30
  def decorator(func: Callable[..., Awaitable[BaseServiceRESTControllerResults]]):
27
31
  @wraps(func)
28
32
  async def wrapper(*args, **kwargs):
@@ -31,53 +35,64 @@ class BaseControllerUtils:
31
35
  bound.apply_defaults()
32
36
 
33
37
  parameters = bound.arguments.get("parameters")
34
- expand: BaseTypes.OptionalListOfStrings = getattr(parameters, 'expand', None)
38
+ expand: BaseTypes.OptionalListOfStrings = getattr(
39
+ parameters, "expand", None
40
+ )
35
41
 
36
- #* Validate expandable fields dependencies
37
- if expand is not None and expandable_fields_dependencies_map is not None:
38
- for dependency, dependents in expandable_fields_dependencies_map.items():
42
+ # * Validate expandable fields dependencies
43
+ if (
44
+ expand is not None
45
+ and expandable_fields_dependencies_map is not None
46
+ ):
47
+ for (
48
+ dependency,
49
+ dependents,
50
+ ) in expandable_fields_dependencies_map.items():
39
51
  if dependency not in expand:
40
52
  for dependent in dependents:
41
53
  if dependent in expand:
42
54
  other = f"'{dependency}' must also be expanded if '{dependent}' is expanded"
43
- content = BaseResponses.InvalidExpand(other=other).model_dump()
55
+ content = BaseResponses.InvalidExpand(other=other).model_dump() # type: ignore
44
56
  return BaseServiceRESTControllerResults(
45
57
  success=False,
46
58
  content=content,
47
- status_code=status.HTTP_400_BAD_REQUEST
48
- )
59
+ status_code=status.HTTP_400_BAD_REQUEST,
60
+ ) # type: ignore
49
61
 
50
- #* Call the original function
62
+ # * Call the original function
51
63
  result = await func(*args, **kwargs)
52
64
 
53
65
  if not isinstance(result.content, Dict):
54
66
  return result
55
67
 
56
- #* Recursive function to apply expansion processors
57
- def recursive_expand(data: Union[Dict, List], expand: BaseTypes.OptionalListOfStrings):
68
+ # * Recursive function to apply expansion processors
69
+ def recursive_expand(
70
+ data: BaseTypes.ListOrDictOfAny,
71
+ expand: BaseTypes.OptionalListOfStrings,
72
+ ) -> BaseTypes.ListOrDictOfAny:
58
73
  if isinstance(data, list):
59
74
  for idx, item in enumerate(data):
60
75
  data[idx] = recursive_expand(item, expand)
61
76
  return data
62
- elif isinstance(data, Dict):
63
- #* Apply each processor to current dict
77
+ elif isinstance(data, dict):
78
+ # * If no expand is provided
79
+ # * Apply each processor to current dict
64
80
  parameters = (
65
- BaseGeneralParametersTransfers
66
- .FieldExpansionProcessor(
67
- data=data,
68
- expand=expand
81
+ BaseGeneralParametersTransfers.FieldExpansionProcessor(
82
+ data=data, expand=expand
69
83
  )
70
84
  )
71
85
  for processor in field_expansion_processors or []:
72
86
  data = processor(parameters)
87
+ data = cast(BaseTypes.StringToAnyDict, data)
73
88
  for key in data.keys():
74
- if isinstance(data[key], (Dict, List)):
89
+ if isinstance(data[key], (dict, list)):
75
90
  data[key] = recursive_expand(data[key], expand)
76
91
  return data
77
92
  else:
78
93
  return data
79
94
 
80
- #* Process expansions recursively if needed
95
+ # * Process expansions recursively if needed
81
96
  if (
82
97
  result.success
83
98
  and result.content.get("data", None) is not None
@@ -85,46 +100,51 @@ class BaseControllerUtils:
85
100
  ):
86
101
  data = result.content["data"]
87
102
  result.content["data"] = recursive_expand(data, expand)
88
- result.process_response()
103
+ result.response
89
104
 
90
105
  return result
106
+
91
107
  return wrapper
108
+
92
109
  return decorator
93
110
 
94
111
  @staticmethod
95
112
  def field_modification_handler(
96
- field_modification_processors: BaseGeneralExpandedTypes.OptionalListOfFieldModificationProcessor = None
113
+ field_modification_processors: BaseGeneralExpandedTypes.OptionalListOfFieldModificationProcessor = None,
97
114
  ):
98
115
  """
99
116
  Decorator to handle expandable fields validation and processing.
100
-
117
+
101
118
  Args:
102
119
  expandable_fields_dependencies_map: Dictionary where keys are dependency fields and values are lists of dependent fields
103
120
  field_modification_processors: List of processor functions that handle that field's data
104
121
  """
122
+
105
123
  def decorator(func: Callable[..., Awaitable[BaseServiceRESTControllerResults]]):
106
124
  @wraps(func)
107
125
  async def wrapper(*args, **kwargs):
108
- #* Call the original function
126
+ # * Call the original function
109
127
  result = await func(*args, **kwargs)
110
128
 
111
129
  if not isinstance(result.content, Dict):
112
130
  return result
113
131
 
114
- #* Recursive function to apply modification processors
115
- def recursive_modify(data: Union[Dict, List]):
132
+ # * Recursive function to apply modification processors
133
+ def recursive_modify(data: BaseTypes.ListOrDictOfAny):
116
134
  if isinstance(data, list):
117
135
  for idx, item in enumerate(data):
118
136
  data[idx] = recursive_modify(item)
119
137
  return data
120
138
  elif isinstance(data, Dict):
121
- #* Apply each processor to current dict
139
+ # * Apply each processor to current dict
122
140
  parameters = (
123
- BaseGeneralParametersTransfers
124
- .FieldModificationProcessor(data=data)
141
+ BaseGeneralParametersTransfers.FieldModificationProcessor(
142
+ data=data
143
+ )
125
144
  )
126
145
  for processor in field_modification_processors or []:
127
146
  data = processor(parameters)
147
+ data = cast(BaseTypes.StringToAnyDict, data)
128
148
  for key in data.keys():
129
149
  if isinstance(data[key], (Dict, List)):
130
150
  data[key] = recursive_modify(data[key])
@@ -132,7 +152,7 @@ class BaseControllerUtils:
132
152
  else:
133
153
  return data
134
154
 
135
- #* Process modifications recursively if needed
155
+ # * Process modifications recursively if needed
136
156
  if (
137
157
  result.success
138
158
  and result.content.get("data", None) is not None
@@ -140,8 +160,10 @@ class BaseControllerUtils:
140
160
  ):
141
161
  data = result.content["data"]
142
162
  result.content["data"] = recursive_modify(data)
143
- result.process_response()
163
+ result.response
144
164
 
145
165
  return result
166
+
146
167
  return wrapper
147
- return decorator
168
+
169
+ return decorator
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
  from .auth import AuthDependencies
3
3
 
4
+
4
5
  class BaseDependencies:
5
- Auth = AuthDependencies
6
+ Auth = AuthDependencies
@@ -4,21 +4,14 @@ from fastapi.security import HTTPAuthorizationCredentials
4
4
  from maleo_foundation.authentication import Authentication
5
5
  from maleo_foundation.authorization import TOKEN_SCHEME, Authorization
6
6
 
7
+
7
8
  class AuthDependencies:
8
9
  @staticmethod
9
- def authentication(
10
- request: Request
11
- ) -> Authentication:
12
- return Authentication(
13
- credentials=request.auth,
14
- user=request.user
15
- )
10
+ def authentication(request: Request) -> Authentication:
11
+ return Authentication(credentials=request.auth, user=request.user)
16
12
 
17
13
  @staticmethod
18
14
  def authorization(
19
- token: HTTPAuthorizationCredentials = Security(TOKEN_SCHEME)
15
+ token: HTTPAuthorizationCredentials = Security(TOKEN_SCHEME),
20
16
  ) -> Authorization:
21
- return Authorization(
22
- scheme=token.scheme,
23
- credentials=token.credentials
24
- )
17
+ return Authorization(scheme=token.scheme, credentials=token.credentials)
@@ -1,9 +1,8 @@
1
1
  from fastapi.requests import Request
2
2
  from maleo_foundation.models.transfers.general.request import RequestContext
3
3
 
4
+
4
5
  class ContextDependencies:
5
6
  @staticmethod
6
- def get_request_context(
7
- request: Request
8
- ) -> RequestContext:
9
- return request.state.request_context
7
+ def get_request_context(request: Request) -> RequestContext:
8
+ return request.state.request_context
@@ -9,61 +9,68 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
9
9
  from sqlalchemy.exc import SQLAlchemyError
10
10
  from typing import Optional
11
11
  from maleo_foundation.models.responses import BaseResponses
12
- from maleo_foundation.models.transfers.results.service.general \
13
- import BaseServiceGeneralResultsTransfers
14
- from maleo_foundation.models.transfers.results.service.repository \
15
- import BaseServiceRepositoryResultsTransfers
12
+ from maleo_foundation.models.transfers.results.service.general import (
13
+ BaseServiceGeneralResultsTransfers,
14
+ )
16
15
  from maleo_foundation.utils.logging import BaseLogger
17
16
 
17
+
18
18
  class BaseExceptions:
19
19
  @staticmethod
20
20
  def authentication_error_handler(request: Request, exc: Exception):
21
21
  return JSONResponse(
22
- content=BaseResponses.Unauthorized(other=str(exc)).model_dump(mode="json"),
23
- status_code=status.HTTP_401_UNAUTHORIZED
22
+ content=BaseResponses.Unauthorized(other=str(exc)).model_dump(mode="json"), # type: ignore
23
+ status_code=status.HTTP_401_UNAUTHORIZED,
24
24
  )
25
25
 
26
26
  @staticmethod
27
- async def validation_exception_handler(request: Request, exc: RequestValidationError):
27
+ async def validation_exception_handler(
28
+ request: Request, exc: RequestValidationError
29
+ ):
28
30
  serialized_error = jsonable_encoder(exc.errors())
29
31
  return JSONResponse(
30
- content=BaseResponses.ValidationError(other=serialized_error).model_dump(mode="json"),
31
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY
32
+ content=BaseResponses.ValidationError(other=serialized_error).model_dump(mode="json"), # type: ignore
33
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
32
34
  )
33
35
 
34
36
  @staticmethod
35
37
  async def http_exception_handler(request: Request, exc: StarletteHTTPException):
36
38
  if exc.status_code in BaseResponses.other_responses:
37
39
  return JSONResponse(
38
- content=BaseResponses.other_responses[exc.status_code]["model"]().model_dump(mode="json"),
39
- status_code=exc.status_code
40
+ content=BaseResponses.other_responses[exc.status_code]["model"]().model_dump(mode="json"), # type: ignore
41
+ status_code=exc.status_code,
40
42
  )
41
43
 
42
44
  return JSONResponse(
43
- content=BaseResponses.ServerError().model_dump(mode="json"),
44
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
45
+ content=BaseResponses.ServerError().model_dump(mode="json"), # type: ignore
46
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
45
47
  )
46
48
 
47
49
  @staticmethod
48
50
  def repository_exception_handler(
49
51
  operation: str,
50
52
  logger: Optional[BaseLogger] = None,
51
- fail_result_class: type[BaseServiceRepositoryResultsTransfers.Fail] = BaseServiceRepositoryResultsTransfers.Fail
53
+ fail_result_class: type[
54
+ BaseServiceGeneralResultsTransfers.Fail
55
+ ] = BaseServiceGeneralResultsTransfers.Fail,
52
56
  ):
53
57
  """Decorator to handle repository-related exceptions consistently for sync and async functions."""
58
+
54
59
  def decorator(func):
55
60
  def _handler(e: Exception, category: str, description: str):
56
61
  if logger:
57
62
  logger.error(
58
63
  f"{category} occurred while {operation}: '{str(e)}'",
59
- exc_info=True
64
+ exc_info=True,
60
65
  )
61
66
  return fail_result_class(
62
67
  message=f"Failed {operation}",
63
68
  description=description,
64
- other=category
65
- )
69
+ other=category,
70
+ ) # type: ignore
71
+
66
72
  if asyncio.iscoroutinefunction(func):
73
+
67
74
  @wraps(func)
68
75
  async def async_wrapper(*args, **kwargs):
69
76
  try:
@@ -72,22 +79,24 @@ class BaseExceptions:
72
79
  return _handler(
73
80
  e,
74
81
  category="Validation error",
75
- description=f"A validation error occurred while {operation}. Please try again later or contact administrator."
82
+ description=f"A validation error occurred while {operation}. Please try again later or contact administrator.",
76
83
  )
77
84
  except SQLAlchemyError as e:
78
85
  return _handler(
79
86
  e,
80
87
  category="Database operation failed",
81
- description=f"A database error occurred while {operation}. Please try again later or contact administrator."
88
+ description=f"A database error occurred while {operation}. Please try again later or contact administrator.",
82
89
  )
83
90
  except Exception as e:
84
91
  return _handler(
85
92
  e,
86
93
  category="Internal processing error",
87
- description=f"An unexpected error occurred while {operation}. Please try again later or contact administrator."
94
+ description=f"An unexpected error occurred while {operation}. Please try again later or contact administrator.",
88
95
  )
96
+
89
97
  return async_wrapper
90
98
  else:
99
+
91
100
  @wraps(func)
92
101
  def sync_wrapper(*args, **kwargs):
93
102
  try:
@@ -96,41 +105,54 @@ class BaseExceptions:
96
105
  return _handler(
97
106
  e,
98
107
  category="Validation error",
99
- description=f"A validation error occurred while {operation}. Please try again later or contact administrator."
108
+ description=f"A validation error occurred while {operation}. Please try again later or contact administrator.",
100
109
  )
101
110
  except SQLAlchemyError as e:
102
111
  return _handler(
103
112
  e,
104
113
  category="Database operation failed",
105
- description=f"A database error occurred while {operation}. Please try again later or contact administrator."
114
+ description=f"A database error occurred while {operation}. Please try again later or contact administrator.",
106
115
  )
107
116
  except Exception as e:
108
117
  return _handler(
109
118
  e,
110
119
  category="Internal processing error",
111
- description=f"An unexpected error occurred while {operation}. Please try again later or contact administrator."
120
+ description=f"An unexpected error occurred while {operation}. Please try again later or contact administrator.",
112
121
  )
122
+
113
123
  return sync_wrapper
124
+
114
125
  return decorator
115
126
 
116
127
  @staticmethod
117
128
  def service_exception_handler(
118
129
  operation: str,
119
130
  logger: Optional[BaseLogger] = None,
120
- fail_result_class: type[BaseServiceGeneralResultsTransfers.Fail] = BaseServiceGeneralResultsTransfers.Fail
131
+ fail_result_class: type[
132
+ BaseServiceGeneralResultsTransfers.Fail
133
+ ] = BaseServiceGeneralResultsTransfers.Fail,
121
134
  ):
122
135
  """Decorator to handle service-related exceptions consistently."""
136
+
123
137
  def decorator(func):
124
138
  @wraps(func)
125
139
  def wrapper(*args, **kwargs):
126
140
  try:
127
141
  return func(*args, **kwargs)
128
142
  except Exception as e:
129
- logger.error("Unexpected error occurred while %s: '%s'", operation, str(e), exc_info=True)
143
+ if logger:
144
+ logger.error(
145
+ "Unexpected error occurred while %s: '%s'",
146
+ operation,
147
+ str(e),
148
+ exc_info=True,
149
+ )
130
150
  return fail_result_class(
131
151
  message=f"Failed {operation}",
132
152
  description=f"An unexpected error occurred while {operation}. Please try again later or contact administrator.",
133
- other="Internal processing error"
134
- )
153
+ other="Internal processing error",
154
+ ) # type: ignore
155
+
135
156
  return wrapper
136
- return decorator
157
+
158
+ return decorator
@@ -1,32 +1,36 @@
1
1
  from datetime import datetime, timezone
2
2
  from fastapi import Request
3
3
  from starlette.requests import HTTPConnection
4
- from uuid import uuid4
4
+ from uuid import UUID, uuid4
5
5
  from maleo_foundation.models.transfers.general.request import RequestContext
6
6
 
7
+
7
8
  def extract_client_ip(conn: HTTPConnection) -> str:
8
9
  """Extract client IP with more robust handling of proxies"""
9
- #* Check for X-Forwarded-For header (common when behind proxy/load balancer)
10
+ # * Check for X-Forwarded-For header (common when behind proxy/load balancer)
10
11
  x_forwarded_for = conn.headers.get("X-Forwarded-For")
11
12
  if x_forwarded_for:
12
- #* The client's IP is the first one in the list
13
+ # * The client's IP is the first one in the list
13
14
  ips = [ip.strip() for ip in x_forwarded_for.split(",")]
14
15
  return ips[0]
15
16
 
16
- #* Check for X-Real-IP header (used by some proxies)
17
+ # * Check for X-Real-IP header (used by some proxies)
17
18
  x_real_ip = conn.headers.get("X-Real-IP")
18
19
  if x_real_ip:
19
20
  return x_real_ip
20
21
 
21
- #* Fall back to direct client connection
22
+ # * Fall back to direct client connection
22
23
  return conn.client.host if conn.client else "unknown"
23
24
 
25
+
24
26
  def extract_request_context(request: Request) -> RequestContext:
25
27
  headers = request.headers
26
28
 
27
29
  request_id = headers.get("x-request-id")
28
30
  if request_id is None:
29
31
  request_id = uuid4()
32
+ else:
33
+ request_id = UUID(request_id)
30
34
 
31
35
  ip_address = extract_client_ip(request)
32
36
 
@@ -42,7 +46,15 @@ def extract_request_context(request: Request) -> RequestContext:
42
46
  path_params=None if not request.path_params else request.path_params,
43
47
  query_params=None if not request.query_params else str(request.query_params),
44
48
  ip_address=ip_address,
45
- is_internal=None if ip_address=="unknown" else (ip_address.startswith("10.") or ip_address.startswith("192.168.") or ip_address.startswith("172.")),
49
+ is_internal=(
50
+ None
51
+ if ip_address == "unknown"
52
+ else (
53
+ ip_address.startswith("10.")
54
+ or ip_address.startswith("192.168.")
55
+ or ip_address.startswith("172.")
56
+ )
57
+ ),
46
58
  user_agent=headers.get("user-agent"),
47
59
  ua_browser=ua_browser,
48
60
  ua_mobile=headers.get("sec-ch-ua-mobile"),
@@ -1,4 +1,5 @@
1
1
  from .case import CaseFormatter
2
2
 
3
+
3
4
  class BaseFormatter:
4
- Case = CaseFormatter
5
+ Case = CaseFormatter
@@ -1,6 +1,7 @@
1
1
  import re
2
2
  from enum import StrEnum
3
3
 
4
+
4
5
  class CaseFormatter:
5
6
  class Case(StrEnum):
6
7
  CAMEL = "camel"
@@ -10,19 +11,19 @@ class CaseFormatter:
10
11
  @staticmethod
11
12
  def to_camel_case(text: str) -> str:
12
13
  """Converts snake_case or PascalCase to camelCase."""
13
- words = re.split(r'[_\s]', text) # Handle snake_case and spaces
14
+ words = re.split(r"[_\s]", text) # Handle snake_case and spaces
14
15
  return words[0].lower() + "".join(word.capitalize() for word in words[1:])
15
16
 
16
17
  @staticmethod
17
18
  def to_pascal_case(text: str) -> str:
18
19
  """Converts snake_case or camelCase to PascalCase."""
19
- words = re.split(r'[_\s]', text)
20
+ words = re.split(r"[_\s]", text)
20
21
  return "".join(word.capitalize() for word in words)
21
22
 
22
23
  @staticmethod
23
24
  def to_snake_case(text: str) -> str:
24
25
  """Converts camelCase or PascalCase to snake_case."""
25
- return re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', text).lower()
26
+ return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", text).lower()
26
27
 
27
28
  @staticmethod
28
29
  def convert(text: str, target: Case) -> str:
@@ -34,4 +35,4 @@ class CaseFormatter:
34
35
  elif target == CaseFormatter.Case.SNAKE:
35
36
  return CaseFormatter.to_snake_case(text)
36
37
  else:
37
- raise ValueError(f"Invalid target case: {target}.")
38
+ raise ValueError(f"Invalid target case: {target}.")
@@ -4,8 +4,9 @@ from .json import JSONLoader
4
4
  from .key import KeyLoaders
5
5
  from .yaml import YAMLLoader
6
6
 
7
+
7
8
  class BaseLoaders:
8
9
  Credential = CredentialLoaders
9
10
  JSON = JSONLoader
10
11
  Key = KeyLoaders
11
- YAML = YAMLLoader
12
+ YAML = YAMLLoader
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
  from .google import GoogleCredentialsLoader
3
3
 
4
+
4
5
  class CredentialLoaders:
5
- Google = GoogleCredentialsLoader
6
+ Google = GoogleCredentialsLoader