aws-python-helper 1.15.2__tar.gz → 1.16.0__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 (69) hide show
  1. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/PKG-INFO +1 -1
  2. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/repository/base.py +60 -15
  3. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/utils/json_encoder.py +10 -1
  4. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper.egg-info/PKG-INFO +1 -1
  5. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper.egg-info/SOURCES.txt +2 -0
  6. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/pyproject.toml +1 -1
  7. aws_python_helper-1.16.0/tests/test_effective_statuses.py +90 -0
  8. aws_python_helper-1.16.0/tests/test_json_encoder.py +51 -0
  9. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/README.md +0 -0
  10. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/__init__.py +0 -0
  11. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/__init__.py +0 -0
  12. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/auth_middleware.py +0 -0
  13. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/auth_validators.py +0 -0
  14. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/base.py +0 -0
  15. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/dispatcher.py +0 -0
  16. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/exceptions.py +0 -0
  17. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/fetcher.py +0 -0
  18. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/handler.py +0 -0
  19. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/api/jwt_keys.py +0 -0
  20. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/context/__init__.py +0 -0
  21. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/context/session.py +0 -0
  22. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/context/state_validator.py +0 -0
  23. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/database/__init__.py +0 -0
  24. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/database/database_proxy.py +0 -0
  25. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/database/external_database_proxy.py +0 -0
  26. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
  27. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/database/mongo_manager.py +0 -0
  28. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/fargate/__init__.py +0 -0
  29. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/fargate/executor.py +0 -0
  30. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/fargate/fetcher.py +0 -0
  31. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/fargate/handler.py +0 -0
  32. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/fargate/task_base.py +0 -0
  33. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/invoker/__init__.py +0 -0
  34. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/invoker/api_client.py +0 -0
  35. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/invoker/exceptions.py +0 -0
  36. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/invoker/lambda_invoker.py +0 -0
  37. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
  38. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/lambda_standalone/base.py +0 -0
  39. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
  40. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
  41. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/model_index_sync/__init__.py +0 -0
  42. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/model_index_sync/base.py +0 -0
  43. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/model_query/__init__.py +0 -0
  44. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/model_query/base.py +0 -0
  45. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/monitoring/__init__.py +0 -0
  46. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/monitoring/run.py +0 -0
  47. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/permissions/__init__.py +0 -0
  48. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/permissions/catalog.py +0 -0
  49. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/repository/__init__.py +0 -0
  50. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/repository/audit.py +0 -0
  51. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/settings.py +0 -0
  52. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/sns/__init__.py +0 -0
  53. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/sns/publisher.py +0 -0
  54. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/sqs/__init__.py +0 -0
  55. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/sqs/consumer_base.py +0 -0
  56. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/sqs/fetcher.py +0 -0
  57. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/sqs/handler.py +0 -0
  58. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/utils/__init__.py +0 -0
  59. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/utils/response.py +0 -0
  60. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper/utils/serializer.py +0 -0
  61. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
  62. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper.egg-info/requires.txt +0 -0
  63. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/aws_python_helper.egg-info/top_level.txt +0 -0
  64. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/setup.cfg +0 -0
  65. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/tests/test_audit_stamp.py +0 -0
  66. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/tests/test_jwt_keys.py +0 -0
  67. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/tests/test_model_query_schema.py +0 -0
  68. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/tests/test_settings.py +0 -0
  69. {aws_python_helper-1.15.2 → aws_python_helper-1.16.0}/tests/test_sns_publisher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.15.2
3
+ Version: 1.16.0
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -149,26 +149,66 @@ class Repository(ABC):
149
149
  @property
150
150
  def statuses(self) -> Optional[List[str]]:
151
151
  """
152
- The values this record's `status` can take.
152
+ Los valores que puede tomar el `status` de este registro.
153
153
 
154
- Every entity carries one, and the same two by default, so a listing can
155
- show the state of anything without knowing what it is looking at. A
156
- repository with its own lifecycle declares it:
154
+ Tres formas, según lo que la colección necesite:
157
155
 
158
- class UserRepository(Repository):
156
+ **No declarar nada.** Se queda con los dos de siempre, `active` e
157
+ `inactive`, que es lo que hace que un listado pueda mostrar el estado de
158
+ cualquier cosa sin saber qué está mirando.
159
+
160
+ **Declarar `statuses`.** Reemplaza a los default: el vocabulario es ese y
161
+ no hay otro. Para una colección cuyo ciclo es completo y ajeno, donde
162
+ "activo" no significaría nada.
163
+
164
+ class SearchedKeyRepository(Repository):
159
165
  @property
160
166
  def statuses(self):
161
- return ["active", "pending", "inactive"]
167
+ return ["queued", "in_progress", "complete", "failed"]
168
+
169
+ **Declarar `extra_statuses`.** Se suman a los default, que siguen
170
+ valiendo. Para una colección que tiene estados propios *además* del
171
+ ciclo de vida del registro.
172
+
173
+ class TaxSaleRepository(Repository):
174
+ @property
175
+ def extra_statuses(self):
176
+ return ["new"] # efectivo: new, active, inactive
162
177
 
163
- A write with a value outside this set is refused, not stored: an
164
- unexpected status reaches a panel as an unknown label and a query as a
165
- silent mismatch.
178
+ Una escritura con un valor fuera del conjunto efectivo se rechaza, no se
179
+ guarda: un status que nadie esperaba llega a un panel como una etiqueta
180
+ desconocida y a una consulta como un silencio.
166
181
 
167
- None for collections where a lifecycle makes no sense — sessions, tokens,
168
- counters.
182
+ `None` para colecciones donde un ciclo de vida no significa nada
183
+ —sesiones, tokens, contadores—.
169
184
  """
170
185
  return list(DEFAULT_STATUSES)
171
186
 
187
+ @property
188
+ def extra_statuses(self) -> Optional[List[str]]:
189
+ """Valores propios que se agregan a los declarados, sin quitarlos.
190
+
191
+ La diferencia con `statuses` es qué pasa con `active` e `inactive`: acá
192
+ siguen valiendo. Ver la nota en `statuses`.
193
+ """
194
+ return None
195
+
196
+ @property
197
+ def effective_statuses(self) -> Optional[List[str]]:
198
+ """Lo que de verdad se acepta, ya resueltas las dos declaraciones.
199
+
200
+ Los extra van primero y sin repetir, porque el primero del conjunto es
201
+ el que recibe un registro nuevo cuando nadie dice otra cosa.
202
+ """
203
+ declared = self.statuses
204
+ extra = self.extra_statuses
205
+
206
+ if declared is None:
207
+ return list(dict.fromkeys(extra)) if extra else None
208
+ if not extra:
209
+ return list(declared)
210
+ return list(dict.fromkeys(list(extra) + list(declared)))
211
+
172
212
  @property
173
213
  def status_field(self) -> str:
174
214
  """Cómo se llama el campo que lleva el ciclo de vida del registro.
@@ -191,9 +231,14 @@ class Repository(ABC):
191
231
 
192
232
  @property
193
233
  def default_status(self) -> Optional[str]:
194
- """What a record gets when nobody says otherwise. First of `statuses`."""
195
- declared = self.statuses
196
- return declared[0] if declared else None
234
+ """Con qué nace un registro cuando nadie dice otra cosa.
235
+
236
+ El primero del conjunto efectivo, que con `extra_statuses` es el primero
237
+ de los propios: una venta nace `new`, no `active`, aunque `active`
238
+ también valga.
239
+ """
240
+ effective = self.effective_statuses
241
+ return effective[0] if effective else None
197
242
 
198
243
  @property
199
244
  def audit(self) -> bool:
@@ -277,7 +322,7 @@ class Repository(ABC):
277
322
  # La envoltura completa las escrituras; todo lo demás pasa de largo.
278
323
  self._collection_cache[key] = (
279
324
  AuditedCollection(
280
- collection, self.statuses, self.default_status, self.status_field
325
+ collection, self.effective_statuses, self.default_status, self.status_field
281
326
  )
282
327
  if self.audit
283
328
  else collection
@@ -3,7 +3,7 @@ MongoDB JSON Encoder - Custom JSON encoder to handle MongoDB types
3
3
  """
4
4
 
5
5
  import json
6
- from datetime import datetime, date
6
+ from datetime import datetime, date, timezone
7
7
  from decimal import Decimal
8
8
  from typing import Any
9
9
 
@@ -68,6 +68,15 @@ class MongoJSONEncoder(json.JSONEncoder):
68
68
 
69
69
  # Python datetime
70
70
  if isinstance(obj, datetime):
71
+ # Sin zona se asume UTC, que es lo que Mongo guarda y lo que el
72
+ # driver devuelve —naive— salvo que se lo configure para otra cosa.
73
+ #
74
+ # Sin esto la cadena sale sin marca y quien la lee la toma como hora
75
+ # local: en un navegador en UTC-5, un servicio que se anunció hace
76
+ # un minuto aparecía anunciándose "dentro de 5 horas". No falla
77
+ # nada, simplemente dice otra cosa.
78
+ if obj.tzinfo is None:
79
+ obj = obj.replace(tzinfo=timezone.utc)
71
80
  return obj.isoformat()
72
81
 
73
82
  # Python date
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.15.2
3
+ Version: 1.16.0
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -59,6 +59,8 @@ aws_python_helper/utils/json_encoder.py
59
59
  aws_python_helper/utils/response.py
60
60
  aws_python_helper/utils/serializer.py
61
61
  tests/test_audit_stamp.py
62
+ tests/test_effective_statuses.py
63
+ tests/test_json_encoder.py
62
64
  tests/test_jwt_keys.py
63
65
  tests/test_model_query_schema.py
64
66
  tests/test_settings.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aws-python-helper"
7
- version = "1.15.2"
7
+ version = "1.16.0"
8
8
  description = "AWS Python Helper Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,90 @@
1
+ """Las tres formas de declarar el vocabulario de `status`."""
2
+
3
+ from aws_python_helper.repository.audit import DEFAULT_STATUSES
4
+ from aws_python_helper.repository.base import Repository
5
+
6
+
7
+ class SinDeclarar(Repository):
8
+ """Se queda con los dos de siempre."""
9
+
10
+ @property
11
+ def collection_name(self):
12
+ return "sin"
13
+
14
+
15
+ class CicloPropio(Repository):
16
+ """Reemplaza: `active` no significaría nada para una búsqueda."""
17
+
18
+ @property
19
+ def collection_name(self):
20
+ return "busquedas"
21
+
22
+ @property
23
+ def statuses(self):
24
+ return ["queued", "in_progress", "complete", "failed"]
25
+
26
+
27
+ class CicloExtra(Repository):
28
+ """Suma: una venta nace `new` y además puede darse de baja."""
29
+
30
+ @property
31
+ def collection_name(self):
32
+ return "ventas"
33
+
34
+ @property
35
+ def extra_statuses(self):
36
+ return ["new"]
37
+
38
+
39
+ class SinCiclo(Repository):
40
+ """Una colección donde un ciclo de vida no significa nada."""
41
+
42
+ @property
43
+ def collection_name(self):
44
+ return "tokens"
45
+
46
+ @property
47
+ def statuses(self):
48
+ return None
49
+
50
+
51
+ class ExtraRepetido(Repository):
52
+ """Agrega uno que ya estaba: no se duplica."""
53
+
54
+ @property
55
+ def collection_name(self):
56
+ return "repetido"
57
+
58
+ @property
59
+ def extra_statuses(self):
60
+ return ["new", "active"]
61
+
62
+
63
+ def test_sin_declarar_quedan_los_default():
64
+ assert SinDeclarar().effective_statuses == list(DEFAULT_STATUSES)
65
+ assert SinDeclarar().default_status == "active"
66
+
67
+
68
+ def test_statuses_reemplaza_a_los_default():
69
+ assert CicloPropio().effective_statuses == [
70
+ "queued", "in_progress", "complete", "failed",
71
+ ]
72
+ assert "active" not in CicloPropio().effective_statuses
73
+
74
+
75
+ def test_extra_se_suma_a_los_default():
76
+ assert CicloExtra().effective_statuses == ["new", "active", "inactive"]
77
+
78
+
79
+ def test_el_primero_es_con_el_que_nace():
80
+ assert CicloPropio().default_status == "queued"
81
+ assert CicloExtra().default_status == "new"
82
+
83
+
84
+ def test_sin_ciclo_no_valida_nada():
85
+ assert SinCiclo().effective_statuses is None
86
+ assert SinCiclo().default_status is None
87
+
88
+
89
+ def test_un_extra_que_ya_era_default_no_se_repite():
90
+ assert ExtraRepetido().effective_statuses == ["new", "active", "inactive"]
@@ -0,0 +1,51 @@
1
+ """Una fecha que sale de acá dice en qué zona está.
2
+
3
+ Mongo guarda UTC y el driver la devuelve sin marca de zona. Mandada así, quien
4
+ la lee la toma como hora local: en un navegador en UTC-5, algo que pasó hace un
5
+ minuto se mostraba como "dentro de 5 horas". No falla nada — dice otra cosa.
6
+
7
+ ../constitution-core/venv/bin/python tests/test_json_encoder.py
8
+ """
9
+
10
+ import json
11
+ import os
12
+ import sys
13
+ from datetime import datetime, timedelta, timezone
14
+
15
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
16
+
17
+ from aws_python_helper.utils.json_encoder import MongoJSONEncoder
18
+
19
+ failures = []
20
+
21
+
22
+ def check(label, condition):
23
+ print(f" {'ok ' if condition else 'FALLA'} {label}")
24
+ if not condition:
25
+ failures.append(label)
26
+
27
+
28
+ print("== una fecha sin zona sale como UTC ==")
29
+ naive = datetime(2026, 10, 1, 17, 25, 55)
30
+ salida = json.loads(json.dumps({"at": naive}, cls=MongoJSONEncoder))["at"]
31
+ print(f" {salida}")
32
+ check("lleva la marca de zona", salida.endswith("+00:00") or salida.endswith("Z"))
33
+ check("no corre la hora", salida.startswith("2026-10-01T17:25:55"))
34
+
35
+ print("\n== una que ya la trae se respeta ==")
36
+ aware = datetime(2026, 10, 1, 17, 25, 55, tzinfo=timezone(timedelta(hours=-5)))
37
+ salida = json.loads(json.dumps({"at": aware}, cls=MongoJSONEncoder))["at"]
38
+ print(f" {salida}")
39
+ check("conserva su propia zona", salida.endswith("-05:00"))
40
+
41
+ print("\n== y una fecha sin hora no se toca ==")
42
+ from datetime import date as _date
43
+ salida = json.loads(json.dumps({"on": _date(2026, 10, 1)}, cls=MongoJSONEncoder))["on"]
44
+ print(f" {salida}")
45
+ check("sigue siendo una fecha", salida == "2026-10-01")
46
+
47
+ print()
48
+ if failures:
49
+ print(f"{len(failures)} fallas")
50
+ sys.exit(1)
51
+ print("all good")