maleo-foundation 0.1.36__tar.gz → 0.1.38__tar.gz

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 (79) hide show
  1. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/PKG-INFO +1 -1
  2. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/service.py +13 -13
  3. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/middlewares/authentication.py +2 -2
  4. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/middlewares/base.py +2 -19
  5. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/utils/__init__.py +4 -2
  6. maleo_foundation-0.1.38/maleo_foundation/utils/extractor.py +20 -0
  7. maleo_foundation-0.1.38/maleo_foundation/utils/loaders/__init__.py +9 -0
  8. maleo_foundation-0.1.38/maleo_foundation/utils/loaders/json.py +14 -0
  9. maleo_foundation-0.1.38/maleo_foundation/utils/loaders/key.py +67 -0
  10. maleo_foundation-0.1.38/maleo_foundation/utils/loaders/yaml.py +14 -0
  11. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation.egg-info/PKG-INFO +1 -1
  12. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation.egg-info/SOURCES.txt +5 -2
  13. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/pyproject.toml +1 -1
  14. maleo_foundation-0.1.36/maleo_foundation/utils/extractor.py +0 -18
  15. maleo_foundation-0.1.36/maleo_foundation/utils/keyloader.py +0 -65
  16. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/README.md +0 -0
  17. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/__init__.py +0 -0
  18. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/authentication.py +0 -0
  19. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/constants.py +0 -0
  20. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/enums.py +0 -0
  21. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/expanded_types/__init__.py +0 -0
  22. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/expanded_types/client.py +0 -0
  23. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/expanded_types/general.py +0 -0
  24. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/expanded_types/query.py +0 -0
  25. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/expanded_types/service.py +0 -0
  26. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/expanded_types/token.py +0 -0
  27. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/extended_types.py +0 -0
  28. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/__init__.py +0 -0
  29. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/client/__init__.py +0 -0
  30. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/client/base.py +0 -0
  31. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/client/google/__init__.py +0 -0
  32. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/client/google/base.py +0 -0
  33. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/client/google/secret.py +0 -0
  34. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/client/google/storage.py +0 -0
  35. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/client/maleo.py +0 -0
  36. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/db.py +0 -0
  37. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/managers/middleware.py +0 -0
  38. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/middlewares/__init__.py +0 -0
  39. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/middlewares/cors.py +0 -0
  40. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/__init__.py +0 -0
  41. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/responses.py +0 -0
  42. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/schemas/__init__.py +0 -0
  43. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/schemas/general.py +0 -0
  44. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/schemas/parameter.py +0 -0
  45. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/schemas/result.py +0 -0
  46. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/schemas/token.py +0 -0
  47. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/table.py +0 -0
  48. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/__init__.py +0 -0
  49. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/general/__init__.py +0 -0
  50. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/general/token.py +0 -0
  51. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/parameters/__init__.py +0 -0
  52. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/parameters/client.py +0 -0
  53. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/parameters/general.py +0 -0
  54. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/parameters/service.py +0 -0
  55. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/parameters/token.py +0 -0
  56. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/__init__.py +0 -0
  57. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/client/__init__.py +0 -0
  58. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/client/controllers/__init__.py +0 -0
  59. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/client/controllers/http.py +0 -0
  60. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/client/service.py +0 -0
  61. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/service/__init__.py +0 -0
  62. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/service/controllers/__init__.py +0 -0
  63. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/service/controllers/rest.py +0 -0
  64. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/service/general.py +0 -0
  65. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/service/query.py +0 -0
  66. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/models/transfers/results/token.py +0 -0
  67. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/services/__init__.py +0 -0
  68. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/services/token.py +0 -0
  69. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/types.py +0 -0
  70. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/utils/controller.py +0 -0
  71. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/utils/exceptions.py +0 -0
  72. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/utils/formatter/__init__.py +0 -0
  73. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/utils/formatter/case.py +0 -0
  74. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/utils/logging.py +0 -0
  75. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation/utils/query.py +0 -0
  76. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation.egg-info/dependency_links.txt +0 -0
  77. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation.egg-info/requires.txt +0 -0
  78. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/maleo_foundation.egg-info/top_level.txt +0 -0
  79. {maleo_foundation-0.1.36 → maleo_foundation-0.1.38}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.1.36
3
+ Version: 0.1.38
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -20,7 +20,8 @@ from maleo_foundation.middlewares.base import RequestProcessor
20
20
  from maleo_foundation.services.token import BaseTokenService
21
21
  from maleo_foundation.types import BaseTypes
22
22
  from maleo_foundation.utils.exceptions import BaseExceptions
23
- from maleo_foundation.utils.keyloader import load_key
23
+ from maleo_foundation.utils.loaders.json import JSONLoader
24
+ from maleo_foundation.utils.loaders.key import KeyLoader
24
25
  from maleo_foundation.utils.logging import GoogleCloudLogging, ServiceLogger, MiddlewareLogger
25
26
 
26
27
  class LogConfig(BaseModel):
@@ -38,6 +39,7 @@ class Settings(BaseSettings):
38
39
  PUBLIC_KEY_PATH:str = Field("/keys/maleo-public-key.pem", description="Maleo's public key path")
39
40
  KEY_PASSWORD:str = Field(..., description="Maleo key's password")
40
41
  CONFIGURATIONS_PATH:str = Field(..., description="Service's configuration file path")
42
+ DB_PASSWORD:str = Field(..., description="Database's password")
41
43
 
42
44
  class Keys(BaseModel):
43
45
  password:str = Field(..., description="Key's password")
@@ -78,7 +80,8 @@ class ServiceConfigurations(BaseModel):
78
80
 
79
81
  class DatabaseConfigurations(BaseModel):
80
82
  username:str = Field("postgres", description="Database user's username")
81
- password:str = Field(..., description="Database user's password")
83
+ password_env:str = Field("DB_PASSWORD", description="Database user's password .env")
84
+ password:str = Field(os.getenv(password_env), description="Database user's password")
82
85
  host:str = Field(..., description="Database's host")
83
86
  port:int = Field(5432, description="Database's port")
84
87
  database:str = Field(..., description="Database")
@@ -254,9 +257,8 @@ class ServiceManager:
254
257
  return self._settings
255
258
 
256
259
  def _load_configs(self) -> None:
257
- with open(self._settings.CONFIGURATIONS_PATH) as f:
258
- data = json.load(f)
259
- self._configs = Configurations.model_validate(data)
260
+ data = JSONLoader.load(self._settings.CONFIGURATIONS_PATH)
261
+ self._configs = Configurations.model_validate(data)
260
262
 
261
263
  @property
262
264
  def configs(self) -> Configurations:
@@ -278,13 +280,11 @@ class ServiceManager:
278
280
 
279
281
  def _load_credentials(self) -> None:
280
282
  #* Load google credentials
281
- with open(self._settings.GOOGLE_CREDENTIALS_PATH) as f:
282
- data = json.load(f)
283
- google = GoogleCredentials.model_validate(data)
283
+ data = JSONLoader.load(self._settings.GOOGLE_CREDENTIALS_PATH)
284
+ google = GoogleCredentials.model_validate(data)
284
285
  #* Load internal credentials
285
- with open(self._settings.INTERNAL_CREDENTIALS_PATH) as f:
286
- data = json.load(f)
287
- internal = InternalCredentials.model_validate(data)
286
+ data = JSONLoader.load(self._settings.INTERNAL_CREDENTIALS_PATH)
287
+ internal = InternalCredentials.model_validate(data)
288
288
  self._credentials = Credentials(google=google, internal=internal)
289
289
 
290
290
  @property
@@ -294,14 +294,14 @@ class ServiceManager:
294
294
  def _parse_keys(self) -> None:
295
295
  #* Parse private key
296
296
  key_type = BaseEnums.KeyType.PRIVATE
297
- private = load_key(
297
+ private = KeyLoader.load_rsa(
298
298
  type=key_type,
299
299
  path=self._settings.PRIVATE_KEY_PATH,
300
300
  password=self._settings.KEY_PASSWORD
301
301
  )
302
302
  #* Parse public key
303
303
  key_type = BaseEnums.KeyType.PUBLIC
304
- public = load_key(
304
+ public = KeyLoader.load_rsa(
305
305
  type=key_type,
306
306
  path=self._settings.PUBLIC_KEY_PATH
307
307
  )
@@ -6,7 +6,7 @@ from typing import Tuple
6
6
  from maleo_foundation.authentication import Credentials, User
7
7
  from maleo_foundation.models.transfers.parameters.token import BaseTokenParametersTransfers
8
8
  from maleo_foundation.services.token import BaseTokenService
9
- from maleo_foundation.utils.extractor import extract_client_ip
9
+ from maleo_foundation.utils.extractor import BaseExtractors
10
10
  from maleo_foundation.utils.logging import MiddlewareLogger
11
11
 
12
12
  class Backend(AuthenticationBackend):
@@ -16,7 +16,7 @@ class Backend(AuthenticationBackend):
16
16
  self._key = key
17
17
 
18
18
  async def authenticate(self, conn:HTTPConnection) -> Tuple[Credentials, User]:
19
- client_ip = extract_client_ip(conn)
19
+ client_ip = BaseExtractors.extract_client_ip(conn)
20
20
  if "Authorization" not in conn.headers:
21
21
  self._logger.info(f"Request | IP: {client_ip} | URL: {conn.url.path} - Result | General: Header did not contain authorization")
22
22
  return Credentials(), User(authenticated=False)
@@ -9,7 +9,7 @@ from fastapi.responses import JSONResponse
9
9
  from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
10
10
  from typing import Awaitable, Callable, Optional, Sequence
11
11
  from maleo_foundation.models.responses import BaseResponses
12
- from maleo_foundation.utils.extractor import extract_client_ip
12
+ from maleo_foundation.utils.extractor import BaseExtractors
13
13
  from maleo_foundation.utils.logging import MiddlewareLogger
14
14
 
15
15
  RequestProcessor = Callable[[Request], Awaitable[Optional[Response]]]
@@ -80,23 +80,6 @@ class BaseMiddleware(BaseHTTPMiddleware):
80
80
  self.last_cleanup = now
81
81
  self.logger.debug(f"Cleaned up request cache. Removed {len(inactive_ips)} inactive IPs. Current tracked IPs: {len(self.requests)}")
82
82
 
83
- def _extract_client_ip(self, request:Request) -> str:
84
- """Extract client IP with more robust handling of proxies"""
85
- #* Check for X-Forwarded-For header (common when behind proxy/load balancer)
86
- x_forwarded_for = request.headers.get("X-Forwarded-For")
87
- if x_forwarded_for:
88
- #* The client's IP is the first one in the list
89
- ips = [ip.strip() for ip in x_forwarded_for.split(",")]
90
- return ips[0]
91
-
92
- #* Check for X-Real-IP header (used by some proxies)
93
- x_real_ip = request.headers.get("X-Real-IP")
94
- if x_real_ip:
95
- return x_real_ip
96
-
97
- #* Fall back to direct client connection
98
- return request.client.host if request.client else "unknown"
99
-
100
83
  def _check_rate_limit(self, client_ip:str) -> bool:
101
84
  """Check if the client has exceeded their rate limit"""
102
85
  with self._lock:
@@ -179,7 +162,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
179
162
  self._cleanup_old_data() #* Run periodic cleanup
180
163
  request_timestamp = datetime.now(tz=timezone.utc) #* Record the request timestamp
181
164
  start_time = time.perf_counter() #* Record the start time
182
- client_ip = extract_client_ip(request) #* Get request IP with improved extraction
165
+ client_ip = BaseExtractors.extract_client_ip(request) #* Get request IP with improved extraction
183
166
 
184
167
  try:
185
168
  #* 1. Rate limit check
@@ -1,13 +1,15 @@
1
1
  from __future__ import annotations
2
2
  from .formatter import BaseFormatter
3
- from .logger import BaseLogger
4
3
  from .exceptions import BaseExceptions
4
+ from .extractor import BaseExtractors
5
+ from .loaders import BaseLoaders
5
6
  from .controller import BaseControllerUtils
6
7
  from .query import BaseQueryUtils
7
8
 
8
9
  class BaseUtils:
9
10
  Formatter = BaseFormatter
10
- Logger = BaseLogger
11
11
  Exceptions = BaseExceptions
12
+ Extractors = BaseExtractors
13
+ Loaders = BaseLoaders
12
14
  Controller = BaseControllerUtils
13
15
  Query = BaseQueryUtils
@@ -0,0 +1,20 @@
1
+ from starlette.requests import HTTPConnection
2
+
3
+ class BaseExtractors:
4
+ @staticmethod
5
+ def extract_client_ip(conn:HTTPConnection) -> str:
6
+ """Extract client IP with more robust handling of proxies"""
7
+ #* Check for X-Forwarded-For header (common when behind proxy/load balancer)
8
+ x_forwarded_for = conn.headers.get("X-Forwarded-For")
9
+ if x_forwarded_for:
10
+ #* The client's IP is the first one in the list
11
+ ips = [ip.strip() for ip in x_forwarded_for.split(",")]
12
+ return ips[0]
13
+
14
+ #* Check for X-Real-IP header (used by some proxies)
15
+ x_real_ip = conn.headers.get("X-Real-IP")
16
+ if x_real_ip:
17
+ return x_real_ip
18
+
19
+ #* Fall back to direct client connection
20
+ return conn.client.host if conn.client else "unknown"
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+ from .json import JSONLoader
3
+ from .key import KeyLoader
4
+ from .yaml import YAMLLoader
5
+
6
+ class BaseLoaders:
7
+ Json = JSONLoader
8
+ Key = KeyLoader
9
+ Yaml = YAMLLoader
@@ -0,0 +1,14 @@
1
+ import json
2
+ from typing import Dict, List, Union, Any
3
+ from pathlib import Path
4
+
5
+ class JSONLoader:
6
+ @staticmethod
7
+ def load(path:Union[Path, str]) -> Union[Dict[str, Any], List[Any]]:
8
+ file_path = Path(path)
9
+
10
+ if not file_path.is_file():
11
+ raise FileNotFoundError(f"File not found: {file_path}")
12
+
13
+ with open(file_path, 'r') as f:
14
+ return json.load(f)
@@ -0,0 +1,67 @@
1
+ from cryptography.hazmat.primitives import serialization
2
+ from pathlib import Path
3
+ from typing import Optional, Union
4
+ from maleo_foundation.enums import BaseEnums
5
+
6
+ class KeyLoader:
7
+ @staticmethod
8
+ def load_rsa(
9
+ type:BaseEnums.KeyType,
10
+ path: Union[str, Path],
11
+ password:Optional[Union[str, bytes]] = None,
12
+ format:BaseEnums.KeyFormatType = BaseEnums.KeyFormatType.STRING,
13
+ ) -> Union[bytes, str]:
14
+ """
15
+ Load an RSA private or public key strictly from a file.
16
+
17
+ Args:
18
+ path (str | Path): Path to the PEM file.
19
+ password (str | bytes | None): Password for encrypted private keys (optional).
20
+
21
+ Returns:
22
+ rsa.RSAPrivateKey | rsa.RSAPublicKey
23
+ """
24
+ if not isinstance(type, BaseEnums.KeyType):
25
+ raise TypeError("Invalid key type")
26
+
27
+ file_path = Path(path)
28
+
29
+ if not file_path.is_file():
30
+ raise FileNotFoundError(f"Key file not found: {file_path}")
31
+
32
+ if password is not None and not isinstance(password, (str, bytes)):
33
+ raise TypeError("Invalid passsword type")
34
+
35
+ if not isinstance(format, BaseEnums.KeyFormatType):
36
+ raise TypeError("Invalid key format type")
37
+
38
+ key_data = file_path.read_bytes()
39
+
40
+ if type == BaseEnums.KeyType.PRIVATE:
41
+ private_key = serialization.load_pem_private_key(
42
+ key_data,
43
+ password=password.encode() if isinstance(password, str) else password,
44
+ )
45
+ private_key_bytes = private_key.private_bytes(
46
+ encoding=serialization.Encoding.PEM,
47
+ format=serialization.PrivateFormat.PKCS8,
48
+ encryption_algorithm=serialization.NoEncryption()
49
+ )
50
+ if format == BaseEnums.KeyFormatType.BYTES:
51
+ return private_key_bytes
52
+ elif format == BaseEnums.KeyFormatType.STRING:
53
+ return private_key_bytes.decode()
54
+
55
+ elif type == BaseEnums.KeyType.PUBLIC:
56
+ public_key = serialization.load_pem_public_key(key_data)
57
+ public_key_bytes = public_key.public_bytes(
58
+ encoding=serialization.Encoding.PEM,
59
+ format=serialization.PublicFormat.SubjectPublicKeyInfo
60
+ )
61
+ if format == BaseEnums.KeyFormatType.BYTES:
62
+ return public_key_bytes
63
+ elif format == BaseEnums.KeyFormatType.STRING:
64
+ return public_key_bytes.decode()
65
+
66
+ else:
67
+ raise ValueError(f"Unsupported key type: {type}")
@@ -0,0 +1,14 @@
1
+ import yaml
2
+ from pathlib import Path
3
+ from typing import Dict, Union
4
+
5
+ class YAMLLoader:
6
+ @staticmethod
7
+ def load(path:Union[Path, str]) -> Dict:
8
+ file_path = Path(path)
9
+
10
+ if not file_path.is_file():
11
+ raise FileNotFoundError(f"File not found: {file_path}")
12
+
13
+ with file_path.open("r") as f:
14
+ return yaml.safe_load(f)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.1.36
3
+ Version: 0.1.38
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -65,8 +65,11 @@ maleo_foundation/utils/__init__.py
65
65
  maleo_foundation/utils/controller.py
66
66
  maleo_foundation/utils/exceptions.py
67
67
  maleo_foundation/utils/extractor.py
68
- maleo_foundation/utils/keyloader.py
69
68
  maleo_foundation/utils/logging.py
70
69
  maleo_foundation/utils/query.py
71
70
  maleo_foundation/utils/formatter/__init__.py
72
- maleo_foundation/utils/formatter/case.py
71
+ maleo_foundation/utils/formatter/case.py
72
+ maleo_foundation/utils/loaders/__init__.py
73
+ maleo_foundation/utils/loaders/json.py
74
+ maleo_foundation/utils/loaders/key.py
75
+ maleo_foundation/utils/loaders/yaml.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maleo_foundation"
7
- version = "0.1.36"
7
+ version = "0.1.38"
8
8
  description = "Foundation package for Maleo"
9
9
  authors = [
10
10
  { name = "Agra Bima Yuda", email = "agra@nexmedis.com" }
@@ -1,18 +0,0 @@
1
- from starlette.requests import HTTPConnection
2
-
3
- def extract_client_ip(conn:HTTPConnection) -> str:
4
- """Extract client IP with more robust handling of proxies"""
5
- #* Check for X-Forwarded-For header (common when behind proxy/load balancer)
6
- x_forwarded_for = conn.headers.get("X-Forwarded-For")
7
- if x_forwarded_for:
8
- #* The client's IP is the first one in the list
9
- ips = [ip.strip() for ip in x_forwarded_for.split(",")]
10
- return ips[0]
11
-
12
- #* Check for X-Real-IP header (used by some proxies)
13
- x_real_ip = conn.headers.get("X-Real-IP")
14
- if x_real_ip:
15
- return x_real_ip
16
-
17
- #* Fall back to direct client connection
18
- return conn.client.host if conn.client else "unknown"
@@ -1,65 +0,0 @@
1
- import pathlib
2
- from cryptography.hazmat.primitives import serialization
3
- from typing import Optional, Union
4
- from maleo_foundation.enums import BaseEnums
5
-
6
- def load_key(
7
- type:BaseEnums.KeyType,
8
- path: Union[str, pathlib.Path],
9
- password:Optional[Union[str, bytes]] = None,
10
- format:BaseEnums.KeyFormatType = BaseEnums.KeyFormatType.STRING,
11
- ) -> Union[bytes, str]:
12
- """
13
- Load an RSA private or public key strictly from a file.
14
-
15
- Args:
16
- path (str | pathlib.Path): Path to the PEM file.
17
- password (str | bytes | None): Password for encrypted private keys (optional).
18
-
19
- Returns:
20
- rsa.RSAPrivateKey | rsa.RSAPublicKey
21
- """
22
- if not isinstance(type, BaseEnums.KeyType):
23
- raise TypeError("Invalid key type")
24
-
25
- file_path = pathlib.Path(path)
26
-
27
- if not file_path.is_file():
28
- raise FileNotFoundError(f"Key file not found: {file_path}")
29
-
30
- if password is not None and not isinstance(password, (str, bytes)):
31
- raise TypeError("Invalid passsword type")
32
-
33
- if not isinstance(format, BaseEnums.KeyFormatType):
34
- raise TypeError("Invalid key format type")
35
-
36
- key_data = file_path.read_bytes()
37
-
38
- if type == BaseEnums.KeyType.PRIVATE:
39
- private_key = serialization.load_pem_private_key(
40
- key_data,
41
- password=password.encode() if isinstance(password, str) else password,
42
- )
43
- private_key_bytes = private_key.private_bytes(
44
- encoding=serialization.Encoding.PEM,
45
- format=serialization.PrivateFormat.PKCS8,
46
- encryption_algorithm=serialization.NoEncryption()
47
- )
48
- if format == BaseEnums.KeyFormatType.BYTES:
49
- return private_key_bytes
50
- elif format == BaseEnums.KeyFormatType.STRING:
51
- return private_key_bytes.decode()
52
-
53
- elif type == BaseEnums.KeyType.PUBLIC:
54
- public_key = serialization.load_pem_public_key(key_data)
55
- public_key_bytes = public_key.public_bytes(
56
- encoding=serialization.Encoding.PEM,
57
- format=serialization.PublicFormat.SubjectPublicKeyInfo
58
- )
59
- if format == BaseEnums.KeyFormatType.BYTES:
60
- return public_key_bytes
61
- elif format == BaseEnums.KeyFormatType.STRING:
62
- return public_key_bytes.decode()
63
-
64
- else:
65
- raise ValueError(f"Unsupported key type: {type}")