maleo-foundation 0.2.7__py3-none-any.whl → 0.2.9__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.
@@ -1,25 +1,26 @@
1
1
  from pydantic import BaseModel, Field
2
2
  from starlette.authentication import AuthCredentials, BaseUser
3
3
  from typing import Optional, Sequence
4
+ from maleo_foundation.enums import BaseEnums
4
5
  from maleo_foundation.models.transfers.general.token import MaleoFoundationTokenGeneralTransfers
5
6
  from maleo_foundation.types import BaseTypes
6
7
 
7
8
  class Credentials(AuthCredentials):
8
9
  def __init__(
9
10
  self,
10
- key:BaseTypes.OptionalString = None,
11
+ token_type:Optional[BaseEnums.TokenType] = None,
11
12
  token:BaseTypes.OptionalString = None,
12
13
  payload:Optional[MaleoFoundationTokenGeneralTransfers.DecodePayload] = None,
13
14
  scopes:Optional[Sequence[str]] = None
14
15
  ) -> None:
15
- self._key = key
16
+ self._token_type = token_type
16
17
  self._token = token
17
18
  self._payload = payload
18
19
  super().__init__(scopes)
19
20
 
20
21
  @property
21
- def key(self) -> BaseTypes.OptionalString:
22
- return self._key
22
+ def token_type(self) -> Optional[BaseEnums.TokenType]:
23
+ return self._token_type
23
24
 
24
25
  @property
25
26
  def token(self) -> BaseTypes.OptionalString:
@@ -0,0 +1,8 @@
1
+ from fastapi import Security
2
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
3
+ from pydantic import BaseModel
4
+
5
+ TOKEN_SCHEME = HTTPBearer()
6
+
7
+ class Authorization(BaseModel):
8
+ token:HTTPAuthorizationCredentials = Security(TOKEN_SCHEME)
@@ -257,24 +257,20 @@ class ServiceManager:
257
257
  def foundation(self) -> MaleoFoundationClientManager:
258
258
  return self._foundation
259
259
 
260
- async def generate_token(self) -> BaseTypes.OptionalString:
261
- raise NotImplementedError()
262
-
263
260
  @property
264
- def token(self) -> str:
261
+ def token(self) -> BaseTypes.OptionalString:
265
262
  payload = MaleoFoundationTokenGeneralTransfers.BaseEncodePayload(
266
263
  iss=None,
267
264
  sub=str(self._maleo_credentials.id),
268
265
  sr="administrator",
269
266
  u_u=self._maleo_credentials.username,
270
267
  u_e=self._maleo_credentials.email,
271
- u_ut="service"
268
+ u_ut="service",
269
+ exp_in=1
272
270
  )
273
271
  parameters = MaleoFoundationTokenParametersTransfers.Encode(key=self._keys.private, password=self._keys.password, payload=payload)
274
272
  result = self._foundation.services.token.encode(parameters=parameters)
275
- if not result.success:
276
- raise RuntimeError("Failed encoding payload into token")
277
- return result.data.token
273
+ return result.data.token if result.success else None
278
274
 
279
275
  def create_app(self, router:APIRouter, lifespan:Optional[Lifespan[AppType]] = None) -> FastAPI:
280
276
  self._loggers.application.info("Creating FastAPI application")
@@ -1,10 +1,10 @@
1
- from datetime import datetime, timezone
2
1
  from fastapi import FastAPI
3
2
  from starlette.authentication import AuthenticationBackend, AuthenticationError
4
3
  from starlette.middleware.authentication import AuthenticationMiddleware
5
4
  from starlette.requests import HTTPConnection
6
5
  from typing import Tuple
7
6
  from maleo_foundation.authentication import Credentials, User
7
+ from maleo_foundation.enums import BaseEnums
8
8
  from maleo_foundation.client.manager import MaleoFoundationClientManager
9
9
  from maleo_foundation.models.schemas import BaseGeneralSchemas
10
10
  from maleo_foundation.models.transfers.parameters.token import MaleoFoundationTokenParametersTransfers
@@ -19,37 +19,56 @@ class Backend(AuthenticationBackend):
19
19
  self._maleo_foundation = maleo_foundation
20
20
 
21
21
  async def authenticate(self, conn:HTTPConnection) -> Tuple[Credentials, User]:
22
- if "Authorization" not in conn.headers:
23
- return Credentials(), User(authenticated=False)
22
+ if "Authorization" in conn.headers:
23
+ auth = conn.headers["Authorization"]
24
+ parts = auth.split()
25
+ if len(parts) != 2 or parts[0] != "Bearer":
26
+ raise AuthenticationError("Invalid Authorization header format")
27
+ scheme, token = parts
28
+ if scheme != 'Bearer':
29
+ raise AuthenticationError("Authorization scheme must be Bearer token")
30
+
31
+ #* Decode token
32
+ decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(key=self._keys.public, token=token)
33
+ decode_token_result = self._maleo_foundation.services.token.decode(parameters=decode_token_parameters)
34
+ if decode_token_result.success:
35
+ payload = decode_token_result.data
36
+ return (
37
+ Credentials(
38
+ token_type=BaseEnums.TokenType.ACCESS,
39
+ token=token,
40
+ payload=payload,
41
+ scopes=["authenticated", payload.sr]
42
+ ),
43
+ User(
44
+ authenticated=True,
45
+ username=payload.u_u,
46
+ email=payload.u_e
47
+ )
48
+ )
24
49
 
25
- auth = conn.headers["Authorization"]
26
- scheme, token = auth.split()
27
- if scheme != 'Bearer':
28
- # raise AuthenticationError("Authorization scheme must be Bearer token")
29
- return Credentials(), User(authenticated=False)
50
+ if "token" in conn.cookies:
51
+ token = conn.cookies["token"]
52
+ #* Decode token
53
+ decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(key=self._keys.public, token=token)
54
+ decode_token_result = self._maleo_foundation.services.token.decode(parameters=decode_token_parameters)
55
+ if decode_token_result.success:
56
+ payload = decode_token_result.data
57
+ return (
58
+ Credentials(
59
+ token_type=BaseEnums.TokenType.REFRESH,
60
+ token=token,
61
+ payload=payload,
62
+ scopes=["authenticated", payload.sr]
63
+ ),
64
+ User(
65
+ authenticated=True,
66
+ username=payload.u_u,
67
+ email=payload.u_e
68
+ )
69
+ )
30
70
 
31
- decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(key=self._keys.public, token=token)
32
- decode_token_result = self._maleo_foundation.services.token.decode(parameters=decode_token_parameters)
33
- if not decode_token_result.success:
34
- # raise AuthenticationError("Invalid Bearer token, unable to decode token")
35
- return Credentials(), User(authenticated=False)
36
- if decode_token_result.data.exp_dt <= datetime.now(tz=timezone.utc):
37
- # raise AuthenticationError("Expired Bearer token, request new or refresh token")
38
- return Credentials(), User(authenticated=False)
39
-
40
- payload = decode_token_result.data
41
- return (
42
- Credentials(
43
- token=token,
44
- payload=payload,
45
- scopes=["authenticated", payload.sr]
46
- ),
47
- User(
48
- authenticated=True,
49
- username=payload.u_u,
50
- email=payload.u_e
51
- )
52
- )
71
+ return Credentials(), User(authenticated=False)
53
72
 
54
73
  def add_authentication_middleware(app:FastAPI, keys:BaseGeneralSchemas.RSAKeys, logger:MiddlewareLogger, maleo_foundation:MaleoFoundationClientManager) -> None:
55
74
  """
@@ -8,10 +8,13 @@ from fastapi import FastAPI, Request, Response, status
8
8
  from fastapi.responses import JSONResponse
9
9
  from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
10
10
  from typing import Awaitable, Callable, Optional, Sequence
11
- from maleo_foundation.authentication import Credentials, User
11
+ from maleo_foundation.authentication import Authentication
12
+ from maleo_foundation.enums import BaseEnums
12
13
  from maleo_foundation.client.manager import MaleoFoundationClientManager
13
14
  from maleo_foundation.models.schemas import BaseGeneralSchemas
14
15
  from maleo_foundation.models.responses import BaseResponses
16
+ from maleo_foundation.models.transfers.general.token import MaleoFoundationTokenGeneralTransfers
17
+ from maleo_foundation.models.transfers.parameters.token import MaleoFoundationTokenParametersTransfers
15
18
  from maleo_foundation.models.transfers.parameters.signature import MaleoFoundationSignatureParametersTransfers
16
19
  from maleo_foundation.utils.extractor import BaseExtractors
17
20
  from maleo_foundation.utils.logging import MiddlewareLogger
@@ -117,6 +120,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
117
120
  def _add_response_headers(
118
121
  self,
119
122
  request:Request,
123
+ authentication:Authentication,
120
124
  response:Response,
121
125
  request_timestamp:datetime,
122
126
  response_timestamp:datetime,
@@ -132,11 +136,22 @@ class BaseMiddleware(BaseHTTPMiddleware):
132
136
  if sign_result.success:
133
137
  response.headers["X-Signature"] = sign_result.data.signature
134
138
  response = self._append_cors_headers(request=request, response=response) #* Re-append CORS headers
139
+ if authentication.user.is_authenticated \
140
+ and authentication.credentials.token_type == BaseEnums.TokenType.REFRESH \
141
+ and authentication.credentials.payload is not None \
142
+ and (response.status_code >= 200 and response.status_code < 300):
143
+ #* Regenerate new authorization
144
+ payload = MaleoFoundationTokenGeneralTransfers.BaseEncodePayload.model_validate(authentication.credentials.payload.model_dump())
145
+ parameters = MaleoFoundationTokenParametersTransfers.Encode(key=self._keys.private, password=self._keys.password, payload=payload)
146
+ result = self._maleo_foundation.services.token.encode(parameters=parameters)
147
+ if result.success:
148
+ response.headers["X-New-Authorization"] = result.data.token
135
149
  return response
136
150
 
137
151
  def _build_response(
138
152
  self,
139
153
  request:Request,
154
+ authentication:Authentication,
140
155
  authentication_info:str,
141
156
  response:Response,
142
157
  request_timestamp:datetime,
@@ -145,7 +160,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
145
160
  log_level:str = "info",
146
161
  client_ip:str = "unknown"
147
162
  ) -> Response:
148
- response = self._add_response_headers(request, response, request_timestamp, response_timestamp, process_time)
163
+ response = self._add_response_headers(request, authentication, response, request_timestamp, response_timestamp, process_time)
149
164
  log_func = getattr(self._logger, log_level)
150
165
  log_func(
151
166
  f"Request {authentication_info} | IP: {client_ip} | Host: {request.client.host} | Port: {request.client.port} | Method: {request.method} | URL: {request.url.path} | "
@@ -156,6 +171,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
156
171
  def _handle_exception(
157
172
  self,
158
173
  request:Request,
174
+ authentication:Authentication,
159
175
  authentication_info:str,
160
176
  error,
161
177
  request_timestamp:datetime,
@@ -183,7 +199,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
183
199
  f"Headers: {dict(request.headers)} - Response | Status: 500 | Exception:\n{json.dumps(error_details, indent=4)}"
184
200
  )
185
201
 
186
- return self._add_response_headers(request, response, request_timestamp, response_timestamp, process_time)
202
+ return self._add_response_headers(request, authentication, response, request_timestamp, response_timestamp, process_time)
187
203
 
188
204
  async def _request_processor(self, request:Request) -> Optional[Response]:
189
205
  return None
@@ -193,17 +209,21 @@ class BaseMiddleware(BaseHTTPMiddleware):
193
209
  request_timestamp = datetime.now(tz=timezone.utc) #* Record the request timestamp
194
210
  start_time = time.perf_counter() #* Record the start time
195
211
  client_ip = BaseExtractors.extract_client_ip(request) #* Get request IP with improved extraction
196
- user:User = request.user
197
- if not user.is_authenticated:
212
+ authentication = Authentication(
213
+ credentials=request.auth,
214
+ user=request.user
215
+ )
216
+ if not authentication.user.is_authenticated:
198
217
  authentication_info = "| Unauthenticated"
199
218
  else:
200
- authentication_info = f"| Username: {user.display_name} | Email:{user.identity}"
219
+ authentication_info = f"| Username: {authentication.user.display_name} | Email:{authentication.user.identity}"
201
220
 
202
221
  try:
203
222
  #* 1. Rate limit check
204
223
  if self._check_rate_limit(client_ip):
205
224
  return self._build_response(
206
225
  request=request,
226
+ authentication=authentication,
207
227
  authentication_info=authentication_info,
208
228
  response=JSONResponse(
209
229
  content=BaseResponses.RateLimitExceeded().model_dump(),
@@ -221,6 +241,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
221
241
  if pre_response is not None:
222
242
  return self._build_response(
223
243
  request=request,
244
+ authentication=authentication,
224
245
  authentication_info=authentication_info,
225
246
  response=pre_response,
226
247
  request_timestamp=request_timestamp,
@@ -234,6 +255,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
234
255
  response = await call_next(request)
235
256
  response = self._build_response(
236
257
  request=request,
258
+ authentication=authentication,
237
259
  authentication_info=authentication_info,
238
260
  response=response,
239
261
  request_timestamp=request_timestamp,
@@ -248,6 +270,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
248
270
  except Exception as e:
249
271
  return self._handle_exception(
250
272
  request=request,
273
+ authentication=authentication,
251
274
  authentication_info=authentication_info,
252
275
  error=e,
253
276
  request_timestamp=request_timestamp,
@@ -27,7 +27,7 @@ class MaleoFoundationTokenGeneralTransfers:
27
27
 
28
28
  class EncodePayload(DecodePayload):
29
29
  iat_dt:datetime = Field(datetime.now(timezone.utc), description="Issued at (datetime)")
30
- exp_in:int = Field(5, ge=5, description="Expires in (integer, minutes)", exclude=True)
30
+ exp_in:int = Field(15, ge=5, description="Expires in (integer, minutes)", exclude=True)
31
31
 
32
32
  @model_validator(mode="before")
33
33
  @classmethod
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+ from .auth import AuthDependencies
3
+
4
+ class BaseDependencies:
5
+ Auth = AuthDependencies
@@ -0,0 +1,14 @@
1
+ from fastapi import Security
2
+ from fastapi.requests import Request
3
+ from fastapi.security import HTTPAuthorizationCredentials
4
+ from maleo_foundation.authentication import Authentication
5
+ from maleo_foundation.authorization import TOKEN_SCHEME, Authorization
6
+
7
+ class AuthDependencies:
8
+ @staticmethod
9
+ def authentication(request:Request) -> Authentication:
10
+ return Authentication(credentials=request.auth, user=request.user)
11
+
12
+ @staticmethod
13
+ def authorization(token:HTTPAuthorizationCredentials = Security(TOKEN_SCHEME)) -> Authorization:
14
+ return Authorization(token=token)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -1,5 +1,6 @@
1
1
  maleo_foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- maleo_foundation/authentication.py,sha256=0HhZsmZM_oMiC71FpnTBLbx2sQzdsI-RgwNqvxvn5zg,1750
2
+ maleo_foundation/authentication.py,sha256=kJfuRKgQY5cjmn0r36z4a4jF4bg7UifZ9D9mgMMnTsw,1840
3
+ maleo_foundation/authorization.py,sha256=jTacP6xqs6zfzn7xeAD4MnLnyNvzvUQYSmoTnLD5I7s,255
3
4
  maleo_foundation/constants.py,sha256=aBmEfWlBqZxi0k-n6h2NM1YRLOjMnheEiLyQcjP-zCQ,1164
4
5
  maleo_foundation/enums.py,sha256=uvwl3dl2r6BoJMEbtSETiLoyJubHup9Lc7VOg7w7zQo,2943
5
6
  maleo_foundation/extended_types.py,sha256=pIKt-_9tby4rmune3fmWcCW_mohaNRh_1lywBmdc-L4,301
@@ -32,7 +33,7 @@ maleo_foundation/expanded_types/encryption/rsa.py,sha256=MuhB_DGrjnsl4t96W4pKuCt
32
33
  maleo_foundation/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
34
  maleo_foundation/managers/db.py,sha256=Pn5EZ-c1Hy6-BihN7KokHJWmBIt3Ty96fZ0zF-srtF4,5208
34
35
  maleo_foundation/managers/middleware.py,sha256=ODIQU1Hpu-Xempjjo_VRbVtxiD5oi74mNuoWuDawRh0,4250
35
- maleo_foundation/managers/service.py,sha256=VggsQUS_i80_fGZYrErBALYXLLFx1JnE4prGJ0L6HjU,16240
36
+ maleo_foundation/managers/service.py,sha256=MEAoL2zab3k0d894zwP4Sem3Na38jS2gFsAiEJUmXZM,16110
36
37
  maleo_foundation/managers/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
38
  maleo_foundation/managers/client/base.py,sha256=5z9l2GN4QASF0-Lft8o5QQ3SRPXqeNZNT1S1CgaE764,4384
38
39
  maleo_foundation/managers/client/maleo.py,sha256=iCM47TLL-RSQ2FkTmHVPdsb2JCd1LebMx6OJvIr4vCQ,2035
@@ -40,8 +41,8 @@ maleo_foundation/managers/client/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
40
41
  maleo_foundation/managers/client/google/base.py,sha256=eIdd6C2BFIu4EyZ1j017VZaJn_nSTPGFftBwQmVAUDA,1366
41
42
  maleo_foundation/managers/client/google/secret.py,sha256=Ski1CHYeA8vjSk2Oc2Pf4CfFrzT_RcA6NEZwza7gM7Y,4464
42
43
  maleo_foundation/managers/client/google/storage.py,sha256=JFqXd9QgusT75KAWyWdin8V6BbbKcbBCrmWDpqg6i3Q,2530
43
- maleo_foundation/middlewares/authentication.py,sha256=hBGaMiCt0CnUw7sg4PZJ3kFJ0OVXvkOR5LhZcz9QUU8,3545
44
- maleo_foundation/middlewares/base.py,sha256=g4cg9gIUveK9zbjhwQtkdefhuoSlv9BUWVCFaSlOClw,13303
44
+ maleo_foundation/middlewares/authentication.py,sha256=RWFjBciFVVyd-Dl-blFymwzS2F-fGXiN7E3W8a4nPy8,4414
45
+ maleo_foundation/middlewares/base.py,sha256=q8RJJkq39QBqU43DkriJCJlri7S7RVIMsArzfcy7T5Q,14825
45
46
  maleo_foundation/middlewares/cors.py,sha256=9uvBvY2N6Vxa9RP_YtESxcWo6Doi6uS0lzAG9iLY7Uc,2288
46
47
  maleo_foundation/models/__init__.py,sha256=AaKehO7c1HyKhoTGRmNHDddSeBXkW-_YNrpOGBu8Ms8,246
47
48
  maleo_foundation/models/responses.py,sha256=iKJs9EFemCuUOc-IxEUwjX9RgNnPf-mw7yGheCGyVDA,4896
@@ -59,7 +60,7 @@ maleo_foundation/models/transfers/__init__.py,sha256=oJLJ3Geeme6vBw7R2Dhvdvg4ziV
59
60
  maleo_foundation/models/transfers/general/__init__.py,sha256=UPIE9l9XXCb6nWzaV3atMgbbCeBeRzsvFyROJuH2d2w,168
60
61
  maleo_foundation/models/transfers/general/key.py,sha256=tLKkXbwNu7Oc1MdKa8-Y7TlBAIk54h_02AmL5Yg2PrQ,751
61
62
  maleo_foundation/models/transfers/general/signature.py,sha256=J9xQy2HjpCQOnES7RJqsUnDgjFPuakQ1mxyfdTdstSE,297
62
- maleo_foundation/models/transfers/general/token.py,sha256=yj5wM9Wvc0BqyRRDtKiyaaLv-JQf51IIA604JWj-Cqw,2697
63
+ maleo_foundation/models/transfers/general/token.py,sha256=77V2YBB-zQppIxFxxrtbHprGzBvGWbXv2Ie72NaoF60,2698
63
64
  maleo_foundation/models/transfers/parameters/__init__.py,sha256=oKW4RPIEISISRjsJzD8lsCGY1HhZRTzshPpWHcJu86k,353
64
65
  maleo_foundation/models/transfers/parameters/client.py,sha256=tn_Hwa-k-Utp5rODe7GylqZB8djIKKupgkUFscYCyLc,4059
65
66
  maleo_foundation/models/transfers/parameters/general.py,sha256=WoekZJCIoAllhXdRIJkNRdNq0QEIn0bteiHJLtzkCxU,579
@@ -98,6 +99,8 @@ maleo_foundation/utils/extractor.py,sha256=SZXVYDHWGaA-Dd1BUydwF2HHdZqexEielS4Cj
98
99
  maleo_foundation/utils/logging.py,sha256=W5Fhk_xAXVqSujaY8mv3hRH4wlQSpUn4ReuMoiKcQa4,7759
99
100
  maleo_foundation/utils/mergers.py,sha256=DniUu3Ot4qkYH_YSw4uD1cn9cfirum4S_Opp8fMkQwA,702
100
101
  maleo_foundation/utils/query.py,sha256=ODQ3adOYQNj5E2cRW9ytbjBz56nEDcnfq8mQ6YZbCCM,4375
102
+ maleo_foundation/utils/dependencies/__init__.py,sha256=0KKGrdfj8Cc5A4SRk_ZBAxzOP795Mizdb4zIBh07KC4,122
103
+ maleo_foundation/utils/dependencies/auth.py,sha256=E5DfxX7iN-R1vG1lAB9m_U1R52wcBhpVWknPEnZEM8Y,592
101
104
  maleo_foundation/utils/formatter/__init__.py,sha256=iKf5YCbEdg1qKnFHyKqqcQbqAqEeRUf8mhI3v3dQoj8,78
102
105
  maleo_foundation/utils/formatter/case.py,sha256=TmvvlfzGdC_omMTB5vAa40TZBxQ3hnr-SYeo0M52Rlg,1352
103
106
  maleo_foundation/utils/loaders/__init__.py,sha256=P_3ycGfeDXFjAi8bE4iLWHxBveqUIdpHgGv-klRWM3s,282
@@ -107,7 +110,7 @@ maleo_foundation/utils/loaders/credential/__init__.py,sha256=qopTKvcMVoTFwyRijeg
107
110
  maleo_foundation/utils/loaders/credential/google.py,sha256=deksZXT5wPhEsSMHbZ3x05WHXxCjLDt76Ns-1Tmhp7g,948
108
111
  maleo_foundation/utils/loaders/key/__init__.py,sha256=hVygcC2ImHc_aVrSrOmyedR8tMUZokWUKCKOSh5ctbo,106
109
112
  maleo_foundation/utils/loaders/key/rsa.py,sha256=gDhyX6iTFtHiluuhFCozaZ3pOLKU2Y9TlrNMK_GVyGU,3796
110
- maleo_foundation-0.2.7.dist-info/METADATA,sha256=VMS1nmB6vuioBkMlZiflqejZ9d0jPbvldXzjNM3HdCA,3418
111
- maleo_foundation-0.2.7.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
112
- maleo_foundation-0.2.7.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
113
- maleo_foundation-0.2.7.dist-info/RECORD,,
113
+ maleo_foundation-0.2.9.dist-info/METADATA,sha256=6NtW8gRCicNq9xqXcJ_IRipxUY6cr85L7-Lq6ioRkxw,3418
114
+ maleo_foundation-0.2.9.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
115
+ maleo_foundation-0.2.9.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
116
+ maleo_foundation-0.2.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5