pyinternet 0.1.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 (59) hide show
  1. pyinternet-0.1.0/PKG-INFO +365 -0
  2. pyinternet-0.1.0/README.md +351 -0
  3. pyinternet-0.1.0/cache/__init__.py +173 -0
  4. pyinternet-0.1.0/cache/_et.py +143 -0
  5. pyinternet-0.1.0/cache/_logs.py +63 -0
  6. pyinternet-0.1.0/cache/_msg.py +9 -0
  7. pyinternet-0.1.0/cache/cache.py +101 -0
  8. pyinternet-0.1.0/cache/connect.py +62 -0
  9. pyinternet-0.1.0/cache/ctx.py +231 -0
  10. pyinternet-0.1.0/cache/handler.py +571 -0
  11. pyinternet-0.1.0/cache/pubsub.py +78 -0
  12. pyinternet-0.1.0/claim/__init__.py +95 -0
  13. pyinternet-0.1.0/claim/_et.py +10 -0
  14. pyinternet-0.1.0/claim/_mem.py +55 -0
  15. pyinternet-0.1.0/claim/_msg.py +10 -0
  16. pyinternet-0.1.0/claim/_timezone.py +39 -0
  17. pyinternet-0.1.0/claim/_utility.py +15 -0
  18. pyinternet-0.1.0/claim/claim.py +464 -0
  19. pyinternet-0.1.0/envar/__init__.py +65 -0
  20. pyinternet-0.1.0/envar/deprecate.py +80 -0
  21. pyinternet-0.1.0/envar/envar.py +215 -0
  22. pyinternet-0.1.0/event/__init__.py +117 -0
  23. pyinternet-0.1.0/event/_et.py +38 -0
  24. pyinternet-0.1.0/event/_logs.py +65 -0
  25. pyinternet-0.1.0/event/_loop.py +32 -0
  26. pyinternet-0.1.0/event/_msg.py +15 -0
  27. pyinternet-0.1.0/event/_timezone.py +28 -0
  28. pyinternet-0.1.0/event/_utility.py +25 -0
  29. pyinternet-0.1.0/event/connect.py +74 -0
  30. pyinternet-0.1.0/event/emiter.py +126 -0
  31. pyinternet-0.1.0/event/event.py +119 -0
  32. pyinternet-0.1.0/event/handler.py +266 -0
  33. pyinternet-0.1.0/event/message.py +112 -0
  34. pyinternet-0.1.0/event/workstate.py +52 -0
  35. pyinternet-0.1.0/middleware/__init__.py +122 -0
  36. pyinternet-0.1.0/middleware/_et.py +33 -0
  37. pyinternet-0.1.0/middleware/_stdrout.py +33 -0
  38. pyinternet-0.1.0/middleware/_timezone.py +26 -0
  39. pyinternet-0.1.0/middleware/_utility.py +32 -0
  40. pyinternet-0.1.0/middleware/autentication.py +104 -0
  41. pyinternet-0.1.0/middleware/autorization.py +147 -0
  42. pyinternet-0.1.0/middleware/cors.py +48 -0
  43. pyinternet-0.1.0/middleware/ephemeral.py +71 -0
  44. pyinternet-0.1.0/middleware/logger.py +190 -0
  45. pyinternet-0.1.0/middleware/recoverer.py +54 -0
  46. pyinternet-0.1.0/middleware/request_id.py +90 -0
  47. pyinternet-0.1.0/middleware/telemetry.py +526 -0
  48. pyinternet-0.1.0/middleware/wrap_write.py +31 -0
  49. pyinternet-0.1.0/nethttp/__init__.py +167 -0
  50. pyinternet-0.1.0/pyinternet.egg-info/PKG-INFO +365 -0
  51. pyinternet-0.1.0/pyinternet.egg-info/SOURCES.txt +57 -0
  52. pyinternet-0.1.0/pyinternet.egg-info/dependency_links.txt +1 -0
  53. pyinternet-0.1.0/pyinternet.egg-info/requires.txt +7 -0
  54. pyinternet-0.1.0/pyinternet.egg-info/top_level.txt +7 -0
  55. pyinternet-0.1.0/pyproject.toml +25 -0
  56. pyinternet-0.1.0/response/__init__.py +68 -0
  57. pyinternet-0.1.0/response/_et.py +65 -0
  58. pyinternet-0.1.0/response/response.py +285 -0
  59. pyinternet-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,365 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyinternet
3
+ Version: 0.1.0
4
+ Summary: Python port of github.com/celsiainternet/elvis-python packages (cache, envar, event, nethttp, response, claim, middleware)
5
+ Author-email: Cesar Galvis Leon <cgalvisleon@gmail.com>
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: redis>=5.0
9
+ Requires-Dist: python-dotenv>=1.0
10
+ Requires-Dist: nats-py>=2.6
11
+ Requires-Dist: PyJWT>=2.8
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=7.0; extra == "dev"
14
+
15
+ # elvis (Python)
16
+
17
+ Port a Python de paquetes Go de [`github.com/celsiainternet/elvis`](../elvis).
18
+ Los nombres de funciones y el orden/tipo de sus parámetros replican el
19
+ original 1:1, para que el código traducido desde Go se lea igual en Python.
20
+
21
+ Paquetes portados hasta ahora:
22
+
23
+ - **`cache`** ← [`elvis/cache`](../elvis/cache)
24
+ - **`envar`** ← [`elvis/envar`](../elvis/envar)
25
+ - **`event`** ← [`elvis/event`](../elvis/event)
26
+ - **`nethttp`** — no es un port de un paquete elvis; ver su sección abajo.
27
+ - **`response`** ← [`elvis/response`](../elvis/response)
28
+ - **`claim`** ← [`elvis/claim`](../elvis/claim)
29
+ - **`middleware`** ← [`elvis/middleware`](../elvis/middleware)
30
+
31
+ ```python
32
+ import envar
33
+ import cache
34
+ import event
35
+
36
+ cache.Load() # usa REDIS_HOST / REDIS_PASSWORD / REDIS_DB
37
+ cache.Set("greeting", "hello", 60) # segundos, no time.Duration
38
+ print(cache.Get("greeting", "")) # "hello"
39
+ cache.Close()
40
+
41
+ print(envar.GetInt(10, "REDIS_POOL_SIZE"))
42
+
43
+ event.Load() # NATS_HOST / NATS_USER / NATS_PASSWORD
44
+ event.Subscribe("orders:created", lambda msg: print(msg.Data))
45
+ event.Publish("orders:created", {"order_id": "1"})
46
+ event.Close()
47
+
48
+ import claim
49
+ import nethttp
50
+ import response
51
+ import middleware
52
+
53
+ token = claim.NewToken("user-1", "myapp", "Jane", "jane", "web", 3600) # 1h, in seconds
54
+
55
+ def hello(w: nethttp.ResponseWriter, r: nethttp.Request) -> None:
56
+ response.JSON(w, r, 200, {"hello": claim.ClientId(r)})
57
+
58
+ chain = middleware.RequestID(middleware.Logger(middleware.Recoverer(middleware.Autentication(hello))))
59
+
60
+ r = nethttp.Request(method="GET", path="/hello")
61
+ r.header.Set("Authorization", f"Bearer {token}")
62
+ w = nethttp.ResponseWriter()
63
+ chain(w, r)
64
+ ```
65
+
66
+ ## Instalación
67
+
68
+ ```bash
69
+ cd elvis
70
+ pip install -e .
71
+ # o, sin instalar el paquete:
72
+ pip install -r requirements.txt
73
+ ```
74
+
75
+ ## `cache`
76
+
77
+ Port de [`elvis/cache`](../elvis/cache) (`cache.go`, `connect.go`, `ctx.go`,
78
+ `handler.go`, `pubsub.go`).
79
+
80
+ ### Variables de entorno
81
+
82
+ | Variable | Uso | Default |
83
+ | ---------------------- | -------------------------------------------------------- | ------- |
84
+ | `REDIS_HOST` | host:puerto de Redis | — |
85
+ | `REDIS_PASSWORD` | password de Redis | — |
86
+ | `REDIS_DB` | número de base de datos | `0` |
87
+ | `REDIS_POOL_SIZE` | tamaño del pool de conexiones | `10` |
88
+ | `REDIS_MIN_IDLE_CONNS` | leída por paridad; redis-py no tiene equivalente directo | `2` |
89
+
90
+ ### Mapeo Go -> Python
91
+
92
+ | Archivo Go | Funciones | Archivo Python |
93
+ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
94
+ | `cache.go` | `Load`, `Close`, `IsLoad`, `HealthCheck`, `FromId`, tipo `Conn` | `cache.py` |
95
+ | `connect.go` | `ConnectTo` | `connect.py` |
96
+ | `ctx.go` | `SetCtx`, `ExpireCtx`, `GetCtx`, `ExistsCtx`, `DeleteCtx`, `IncrCtx`, `DecrCtx`, `LPushCtx`, `LRemCtx`, `LRangeCtx`, `LTrimCtx`, `HSetCtx`, `HGetCtx`, `HDeleteCtx`, `IncrSetTTLScript`, `DecrKeepTTLScript` | `ctx.py` |
97
+ | `handler.go` | `GenId`, `GenKey`, `Set`, `Get`, `Exists`, `Delete`, `Expire`, `Incr`, `Decr`, `LPush`, `LRem`, `LRange`, `LTrim`, `SetH/D/W/M/Y`, `Empty`, `More`, `HSet`, `HGet`, `HSetAtrib`, `HGetAtrib`, `HDelete`, `SetVerify`, `GetVerify`, `DeleteVerify`, `AllCache`, `GetInt`, `GetInt64`, `GetFloat`, `GetBool`, `GetJson`, `GetItem`, `GetItems`, `IsNil`, `HandlerAll`, `HandlerGet`, `HandlerDelete` | `handler.py` |
98
+ | `pubsub.go` | tipo `Message`, métodos `Pub`, `Sub`, `Unsub` en `Conn` | `pubsub.py` |
99
+ | `et.Json/Item/Items/List` (dependencia) | tipos mínimos usados por `Set`/`GetJson`/`GetItem`/`GetItems`/`AllCache` | `_et.py` |
100
+
101
+ ### Desviaciones deliberadas frente al original en Go
102
+
103
+ - **Manejo de errores**: Go devuelve `(valor, error)`; Python lanza excepciones
104
+ (`RuntimeError` cuando no hay conexión, igual que
105
+ `msg.ERR_NOT_CACHE_SERVICE`). El resto de la firma (nombre y parámetros) no cambia.
106
+ - **`second time.Duration` -> `second: float`**: en Python se pasan segundos
107
+ como `float`/`int` en vez de `time.Duration`.
108
+ - **`def` como nombre de parámetro**: es palabra reservada en Python; se usa
109
+ `def_` (documentado en cada docstring).
110
+ - **`context.Context`**: las funciones `*Ctx` conservan `ctx` como primer
111
+ parámetro por fidelidad de firma, pero no se usa — pásese `None`. El
112
+ cliente `redis-py` síncrono no necesita cancelación/deadlines explícitos.
113
+ - **`cache.conn`**: en Go es una variable de paquete no exportada
114
+ (minúscula); en Python tampoco se expone en `cache.__all__`. Use
115
+ `IsLoad()` / `HealthCheck()`.
116
+ - **`HandlerAll` / `HandlerGet` / `HandlerDelete`**: en Go son
117
+ `http.HandlerFunc` atados a chi/net-http. Python no tiene un framework web
118
+ dominante único, así que aquí reciben un `dict` de query params (el
119
+ equivalente de `response.GetQuery(r)`) y devuelven `(status_code, body)`
120
+ en vez de escribir en un `http.ResponseWriter`.
121
+
122
+ ## `envar`
123
+
124
+ Port completo de [`elvis/envar`](../elvis/envar) (`envar.go` +
125
+ `deprecate.go`, incluidas las funciones marcadas como deprecadas en Go —
126
+ se portaron igual, por completitud).
127
+
128
+ ### Mapeo Go -> Python
129
+
130
+ | Archivo Go | Funciones | Archivo Python |
131
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
132
+ | `envar.go` | interfaz `Store`, `Load`, `MetaSet`, `SetStr`, `SetInt`, `SetInt64`, `SetBool`, `UpSetStr`, `UpSetInt`, `UpSetFloat`, `UpSetBool`, `GetStr`, `GetInt`, `GetInt64`, `GetFloat64`, `GetBool` | `envar.py` |
133
+ | `deprecate.go` | `SetvarStr`, `SetvarInt`, `SetvarBool`, `EnvarStr`, `EnvarInt`, `EnvarInt64`, `EnvarNumber`, `EnvarBool` | `deprecate.py` |
134
+
135
+ `cache` consume este paquete directamente (`cache/connect.py` hace
136
+ `import envar`) en vez de duplicar su propia copia — igual que en Go, donde
137
+ `elvis/cache` importa `elvis/envar`.
138
+
139
+ ### Desviaciones deliberadas frente al original en Go
140
+
141
+ - **Autoload de `.env`**: Go carga `.env` mediante el import en blanco
142
+ `_ "github.com/joho/godotenv/autoload"`; aquí se replica con
143
+ `python-dotenv`'s `load_dotenv()`, ejecutado una vez al importar `envar`.
144
+ - **`Store` interface**: en Go es una `interface`; en Python es un
145
+ `typing.Protocol` con el mismo método `Get(name, _default) -> str`. La
146
+ firma real de `GetStr`/`GetBool` invoca `store.Get(_default, _var)` — el
147
+ mismo orden (aparentemente invertido respecto a los nombres del
148
+ parámetro) que el código Go original, replicado tal cual.
149
+ - **`EnvarStr`/`EnvarInt`/... (deprecadas)** leen `os.environ` directo y
150
+ **no** consultan el `Store` fijado con `Load()`, igual que en Go — a
151
+ diferencia de `GetStr` y compañía, que sí lo consultan.
152
+
153
+ ## `event`
154
+
155
+ Port de [`elvis/event`](../elvis/event) (`event.go`, `connect.go`,
156
+ `emiter.go`, `handler.go`, `message.go`, `msg.go`, `workState.go`). Tiene
157
+ dos modos, igual que en Go:
158
+
159
+ - **Distribuido, vía NATS**: `event.Load()` / `event.Publish(channel, data)`
160
+ / `event.Subscribe(channel, f)` / `event.Stack(channel, f)`.
161
+ - **Local, en el mismo proceso**: `event.On(channel, handler)` /
162
+ `event.Emit(channel, data)`, vía `EventEmiter` — no toca NATS.
163
+
164
+ ### Variables de entorno
165
+
166
+ | Variable | Uso | Default |
167
+ | --------------- | ------------------------------------------------------------ | ---------------- |
168
+ | `NATS_HOST` | host/URL del servidor NATS | — |
169
+ | `NATS_USER` | usuario NATS | — |
170
+ | `NATS_PASSWORD` | password NATS | — |
171
+ | `STAGE` | usada por `Publish` para el prefijo `pipe:<stage>:<channel>` | `local` |
172
+ | `TIMEZONE` | zona horaria para timestamps (`created_at`, etc.) | `America/Bogota` |
173
+
174
+ ### Mapeo Go -> Python
175
+
176
+ | Archivo Go | Funciones | Archivo Python |
177
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------- |
178
+ | `event.go` | constantes `EVENT*`, `Load`, `Close`, `Id`, `HealthCheck`, tipo `Conn` | `event.py` |
179
+ | `connect.go` | `ConnectTo` | `connect.py` |
180
+ | `emiter.go` | `Handler`, `EventEmiter`, `NewEventEmiter`, `On`, `Emit` | `emiter.py` |
181
+ | `message.go` | interfaz `Message`, `EvenMessage` (`Encode`, `ToString`, `ToJson`), `NewEvenMessage`, `DecodeMessage` | `message.py` |
182
+ | `workState.go` | `WorkStatus` (`String`), `WorkStatusPending/Accepted/Processing/Completed/Failed`, `ToWorkStatus` | `workstate.py` |
183
+ | `handler.go` | `Publish`, `Subscribe`, `Queue`, `Stack`, `Unsubscribe`, `Work`, `WorkState`, `Source`, `Log`, `Overflow`, `HttpEventWork` | `handler.py` |
184
+ | `msg.go` | constantes de error (`ERR_NOT_CONNECT`, `ERR_CHANNEL_REQUIRED`, ...) | `_msg.py` |
185
+
186
+ ### Desviaciones deliberadas frente al original en Go
187
+
188
+ - **Cliente NATS asíncrono**: `nats.go` (usado en Go) es síncrono; el
189
+ cliente oficial en Python (`nats-py`) es exclusivamente `asyncio`. Para
190
+ que `Load`, `Publish`, `Subscribe`, etc. sigan siendo llamadas bloqueantes
191
+ normales (igual que en Go), cada `Conn` levanta un event loop en un hilo
192
+ de fondo (`event/_loop.py`) y cada llamada a NATS se despacha ahí vía
193
+ `asyncio.run_coroutine_threadsafe(...).result()`. Detalle interno, no
194
+ cambia la firma pública.
195
+ - **Manejo de errores mixto, igual que en Go**: `Publish` (y todo lo que
196
+ se apoya en ella: `Work`, `WorkState`, `Source`, `Log`, `Overflow`) hace
197
+ _no-op_ silencioso si no hay conexión — así se comporta el Go original
198
+ (`if conn == nil { return nil }`). En cambio `Subscribe`, `Queue`,
199
+ `Stack` y `Unsubscribe` lanzan `RuntimeError(ERR_NOT_CONNECT)`, tal como
200
+ el Go original devuelve `error` explícito en esos casos.
201
+ - **`go Publish(...)` → `threading.Thread(..., daemon=True).start()`**:
202
+ `WorkState`, `Source`, `Log` y `Overflow` disparan la publicación en un
203
+ hilo de fondo, igual que Go las dispara en una goroutine. `Work` en
204
+ cambio publica de forma síncrona, igual que en Go (no usa `go`).
205
+ - **`HttpEventWork`**: recibe un `dict` (equivalente de
206
+ `response.GetBody(r)`) y devuelve `(status_code, body)` en vez de
207
+ `http.ResponseWriter`/`*http.Request` — misma convención que
208
+ `cache.HandlerAll/Get/Delete`. Replica también el orden de validación
209
+ del original: `Work(...)` se ejecuta _antes_ de comprobar que `event` y
210
+ `data` no estén vacíos, así que ya se publicó el evento aunque la
211
+ respuesta termine siendo 400.
212
+ - **`et.Object(m)` en `EvenMessage.ToJson`**: en Go usa reflexión genérica;
213
+ aquí se construye el `dict` directamente a partir de los campos
214
+ conocidos de `EvenMessage` (mismo resultado, sin necesitar reflexión).
215
+
216
+ ## `nethttp`
217
+
218
+ **No es un port de un paquete de `elvis`.** `claim`, `response` y
219
+ `middleware` en Go se construyen directamente sobre la librería estándar
220
+ (`*http.Request`, `http.ResponseWriter`, `http.Header`) en vez de definir
221
+ sus propios tipos de request/response — no hay un paquete `elvis/nethttp`
222
+ que portar. Python no tiene un único framework HTTP dominante, así que
223
+ este módulo hace las veces de esa porción de `net/http` que los tres
224
+ paquetes comparten: `Header` (case-insensitive), `Request`, `ResponseWriter`
225
+ (ya con `Status()`/`BytesWritten()` incorporados) y los códigos de estado
226
+ usados (`StatusOK`, `StatusUnauthorized`, etc. + `StatusText`). Conéctelo a
227
+ Flask/FastAPI/Django/WSGI/ASGI según se necesite. También define
228
+ `Handler`/`Middleware` como los equivalentes de `http.Handler` y
229
+ `func(http.Handler) http.Handler`.
230
+
231
+ ## `response`
232
+
233
+ Port de [`elvis/response`](../elvis/response) (`response.go`), construido
234
+ sobre `nethttp.Request`/`nethttp.ResponseWriter`.
235
+
236
+ ### Mapeo Go -> Python
237
+
238
+ | Función Go | Función Python |
239
+ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
240
+ | `ScanBody`, `ScanStr`, `ScanJson` | igual (`ScanBody` toma `bytes` en vez de `io.Reader`) |
241
+ | `GetBody`, `GetArray`, `GetQuery`, `GetHeader`, `GetParam` | igual |
242
+ | `WriteResponse`, `RESULT`, `ANY`, `JSON`, `ITEM`, `ITEMS` | igual |
243
+ | `HTTPError`, `HTTPAlert`, `Unauthorized`, `Forbidden`, `InternalServerError`, `PreconditionRequired` | igual |
244
+ | `Stream` | igual; `DataFunction` es `Callable[[page, rows], (ok, items)]` en vez de devolver `et.Items` |
245
+ | `HTTPApp` | **no portado** — sirve archivos estáticos vía `chi.Router`/`http.FileSystem`; es plomería de router/filesystem, no lógica de formateo de respuesta, y no hay un equivalente framework-agnóstico razonable |
246
+
247
+ ## `claim`
248
+
249
+ Port de [`elvis/claim`](../elvis/claim) (`claim.go`, `msg.go`): emisión,
250
+ parseo y validación de JWT, respaldado por `cache` (Redis) para poder
251
+ invalidar tokens.
252
+
253
+ ### Variables de entorno
254
+
255
+ | Variable | Uso | Default |
256
+ | -------- | -------------------- | -------- |
257
+ | `SECRET` | clave de firma HS256 | `"1977"` |
258
+
259
+ ### Desviaciones deliberadas frente al original en Go
260
+
261
+ - **`Duration` en segundos**, no el `time.Duration` (nanosegundos) crudo de
262
+ Go — consistente con la convención `second: float` usada en todo este
263
+ port (`cache`, `event`).
264
+ - **`context.Context` → `Request.context`**: los accesores
265
+ (`Token`, `ServiceId`, `ClientId`, `GetClient`, ...) leen de
266
+ `nethttp.Request.context` (un `dict` simple) en vez de un
267
+ `context.Context` tipado por separado.
268
+ - **`mem` no se portó completo**: `ValidToken` usa una caché local L1 en
269
+ memoria antes de golpear Redis; en vez de portar el paquete completo
270
+ `elvis/mem` (750+ líneas, no solicitado), `claim/_mem.py` implementa solo
271
+ el `Get`/`Set`/`Del` con TTL que `ValidToken` necesita.
272
+ - **`ValidToken` devuelve `None`, no lanza**, cuando el token no coincide
273
+ con el guardado en caché — replica exactamente que el Go original
274
+ retorna `(nil, nil)` en ese caso (el llamador debe comprobar
275
+ `if clm is None`, igual que Go comprueba `if clm == nil`).
276
+
277
+ ## `middleware`
278
+
279
+ Port de [`elvis/middleware`](../elvis/middleware) (los 9 archivos:
280
+ `autentication.go`, `autorization.go`, `cors.go`, `ephemeral.go`,
281
+ `logger.go`, `recoverer.go`, `request_id.go`, `telemetry.go`,
282
+ `wrap_write.go`). Cada middleware tiene la forma `nethttp.Middleware`
283
+ (`Callable[[Handler], Handler]`), igual que Go's `func(next http.Handler) http.Handler`.
284
+
285
+ ### Mapeo Go -> Python
286
+
287
+ | Archivo Go | Contenido | Archivo Python |
288
+ | ------------------ | ----------------------------------------------------------------------------------------------------------- | ------------------ |
289
+ | `autentication.go` | `GetAuthorization`, `Autentication` (nombre con el typo del Go original, preservado) | `autentication.py` |
290
+ | `autorization.go` | `Store`, `SetAuthorizationStore`, `Authorization` | `autorization.py` |
291
+ | `cors.go` | `AllowAll` | `cors.py` |
292
+ | `ephemeral.go` | `Ephemeral` | `ephemeral.py` |
293
+ | `logger.go` | `Logger`, `RequestLogger`, `LogFormatter`, `LogEntry`, `GetLogEntry`, `WithLogEntry`, `DefaultLogFormatter` | `logger.py` |
294
+ | `recoverer.go` | `Recoverer`, `PrintPrettyStack` | `recoverer.py` |
295
+ | `request_id.go` | `RequestID`, `RequestIDHeader`, `GetReqID`, `NextRequestID` | `request_id.py` |
296
+ | `telemetry.go` | `Metrics`, `Telemetry`, `NewMetric`, `NewRpcMetric`, `SetServiceName`, `PushTelemetry*` | `telemetry.py` |
297
+ | `wrap_write.go` | `WrapResponseWriter`, `NewWrapResponseWriter` | `wrap_write.py` |
298
+
299
+ ### Desviaciones deliberadas frente al original en Go
300
+
301
+ - **`cors.go`**: Go configura la librería externa `github.com/rs/cors`;
302
+ aquí `AllowAll` implementa directamente la misma política que esa
303
+ configuración expresa (orígenes permitidos o todos si la lista está
304
+ vacía; `HEAD/GET/POST/PUT/PATCH/DELETE`; todos los headers; credenciales
305
+ permitidas), como un `nethttp.Middleware` plano.
306
+ - **`wrap_write.go`**: `nethttp.ResponseWriter` ya trackea
307
+ `Status()`/`BytesWritten()` nativamente (no envuelve un socket real), así
308
+ que `NewWrapResponseWriter` es una función identidad; no se portan
309
+ `Tee`/`Unwrap` ni la detección de `Flusher`/`Hijacker`/`ReaderFrom`
310
+ (irrelevantes sin un socket que envolver).
311
+ - **`recoverer.go`**: `PrintPrettyStack` imprime el traceback nativo de
312
+ Python (`traceback` de la stdlib) en vez de parsear el formato de texto
313
+ específico de `debug.Stack()` de Go; `prettyStack.parse/decorateLine/...`
314
+ no se portan por la misma razón (formato de stack ajeno, no aplica).
315
+ - **`logger.go`**: sin `stdrout` (no solicitado/portado), el color ANSI
316
+ usa un helper mínimo propio (`middleware/_stdrout.py`, no un port). El
317
+ campo `Logger LoggerInterface` de `DefaultLogFormatter` se cae — este
318
+ port imprime directo a stdout. `DefaultLogger` (paquete-variable
319
+ reconfigurable en Go) no se reexporta como `middleware.DefaultLogger`
320
+ porque Python no tiene bindings "vivos" entre módulos: reasignar
321
+ `middleware.DefaultLogger = x` no afectaría lo que `Logger()` realmente
322
+ usa. Para personalizarlo, asigne `middleware.logger.DefaultLogger`
323
+ directamente.
324
+ - **`autorization.go`**: se preserva tal cual el bug del original — el
325
+ chequeo está invertido (`if store is not None: ...error...` en vez de
326
+ `is None`), así que `Authorization` nunca puede autorizar exitosamente
327
+ ninguna solicitud tal como está escrito en Go. Además, si `store` queda
328
+ en `None` y el token es válido, Go entra en pánico al invocar un método
329
+ sobre una interfaz `nil`; este port en cambio captura esa falla y
330
+ responde 500 en vez de propagar/crashear — ver nota en
331
+ `middleware/autorization.py`.
332
+ - **`ephemeral.go`**: usa `claim.ParceToken` (solo valida el JWT), no
333
+ `claim.ValidToken` — no consulta Redis, igual que en Go.
334
+ - **Manejo de errores**: en general, excepciones (`ValueError`,
335
+ `RuntimeError`) en vez de `(valor, error)`; los middlewares las capturan
336
+ con `try/except` en los mismos puntos donde Go comprueba `if err != nil`.
337
+
338
+ ## Tests
339
+
340
+ ```bash
341
+ pip install -e ".[dev]"
342
+ python -m pytest tests/ -v
343
+ ```
344
+
345
+ - `tests/cache/` — tests de `cache`, con un cliente Redis falso en memoria
346
+ (`tests/cache/fake_redis.py`), sin requerir un servidor Redis real.
347
+ - `tests/envar/` — tests de `envar`, usando `monkeypatch` de pytest para
348
+ variables de entorno y `sys.argv`.
349
+ - `tests/event/` — tests de `event`, con un cliente NATS falso en memoria
350
+ (`tests/event/fake_nats.py`) para `Load`/`Publish`/`Subscribe`, más
351
+ cobertura directa de `EventEmiter`, `EvenMessage` y `WorkStatus`. No
352
+ requiere un servidor NATS real.
353
+ - `tests/nethttp/`, `tests/response/` — tests de los tipos HTTP base y del
354
+ formateo de respuestas.
355
+ - `tests/claim/`, `tests/middleware/` — tests de JWT y de la cadena de
356
+ middlewares completa, reutilizando el cliente Redis falso de
357
+ `tests/cache/fake_redis.py` vía fixtures (`cache_conn`) en sus
358
+ respectivos `conftest.py`.
359
+
360
+ cat > ~/.pypirc <<'EOF'
361
+ [pypi]
362
+ username = cgalvisleon
363
+ password = pypi-AgEIcHlwaS5vcmcCJGVlZGU4MjRkLTRkZjYtNDYwYS04OTQ2LTM5NmZjNTRmMDhjZQACKlszLCI1ODJlYTIwNC1mYWM2LTQ5MWQtYTM4NC0yODI1YjY4N2ZhYWIiXQAABiASzdbHGHhllL0mlOsMDVr22p-QJbgcN9HbGzF9YdB_dQ
364
+ EOF
365
+ chmod 600 ~/.pypirc