svc-infra 0.1.626__py3-none-any.whl → 0.1.628__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.

Potentially problematic release.


This version of svc-infra might be problematic. Click here for more details.

@@ -1172,6 +1172,9 @@ def ensure_problem_examples_mutator():
1172
1172
  continue
1173
1173
  if ic < 400:
1174
1174
  continue
1175
+ # Do not add content if response is a $ref; avoid creating siblings
1176
+ if "$ref" in resp:
1177
+ continue
1175
1178
  content = resp.setdefault("content", {})
1176
1179
  # prefer problem+json but also set application/json if present
1177
1180
  for mt in ("application/problem+json", "application/json"):
@@ -34,7 +34,12 @@ def add_probes(
34
34
  app.include_router(router)
35
35
 
36
36
 
37
- def add_maintenance_mode(app: FastAPI, *, env_var: str = "MAINTENANCE_MODE") -> None:
37
+ def add_maintenance_mode(
38
+ app: FastAPI,
39
+ *,
40
+ env_var: str = "MAINTENANCE_MODE",
41
+ exempt_prefixes: tuple[str, ...] | None = None,
42
+ ) -> None:
38
43
  """Enable a simple maintenance gate controlled by an env var.
39
44
 
40
45
  When MAINTENANCE_MODE is truthy, all non-GET requests return 503.
@@ -44,6 +49,9 @@ def add_maintenance_mode(app: FastAPI, *, env_var: str = "MAINTENANCE_MODE") ->
44
49
  async def _maintenance_gate(request: Request, call_next): # noqa: ANN001, ANN202
45
50
  flag = str(os.getenv(env_var, "")).lower() in {"1", "true", "yes", "on"}
46
51
  if flag and request.method not in {"GET", "HEAD", "OPTIONS"}:
52
+ path = request.scope.get("path", "")
53
+ if exempt_prefixes and any(path.startswith(p) for p in exempt_prefixes):
54
+ return await call_next(request)
47
55
  return JSONResponse({"detail": "maintenance"}, status_code=503)
48
56
  return await call_next(request)
49
57
 
svc_infra/jobs/queue.py CHANGED
@@ -69,5 +69,13 @@ class InMemoryJobQueue:
69
69
  job.last_error = error
70
70
  # Exponential backoff: base * attempts
71
71
  delay = job.backoff_seconds * max(1, job.attempts)
72
- job.available_at = now + timedelta(seconds=delay)
72
+ if delay > 0:
73
+ # Add a tiny fudge so an immediate subsequent poll in ultra-fast
74
+ # environments (like our acceptance API) doesn't re-reserve the job.
75
+ # This keeps tests deterministic without impacting semantics.
76
+ job.available_at = now + timedelta(seconds=delay, milliseconds=250)
77
+ else:
78
+ # When backoff is explicitly zero (e.g., unit tests forcing
79
+ # immediate retry), make the job available right away.
80
+ job.available_at = now
73
81
  return
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import datetime, timezone
5
5
  from typing import Dict, List
6
-
7
6
  from uuid import uuid4
8
7
 
9
8
  from svc_infra.db.outbox import OutboxStore
@@ -22,7 +21,16 @@ class InMemoryWebhookSubscriptions:
22
21
  self._subs: Dict[str, List[WebhookSubscription]] = {}
23
22
 
24
23
  def add(self, topic: str, url: str, secret: str) -> None:
25
- self._subs.setdefault(topic, []).append(WebhookSubscription(topic, url, secret))
24
+ # Upsert semantics per (topic, url): if a subscription already exists
25
+ # for this topic and URL, rotate its secret instead of adding a new row.
26
+ # This mirrors typical real-world secret rotation flows where the
27
+ # endpoint remains the same but the signing secret changes.
28
+ lst = self._subs.setdefault(topic, [])
29
+ for sub in lst:
30
+ if sub.url == url:
31
+ sub.secret = secret
32
+ return
33
+ lst.append(WebhookSubscription(topic, url, secret))
26
34
 
27
35
  def get_for_topic(self, topic: str) -> List[WebhookSubscription]:
28
36
  return list(self._subs.get(topic, []))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: svc-infra
3
- Version: 0.1.626
3
+ Version: 0.1.628
4
4
  Summary: Infrastructure for building and deploying prod-ready services
5
5
  License: MIT
6
6
  Keywords: fastapi,sqlalchemy,alembic,auth,infra,async,pydantic
@@ -88,11 +88,11 @@ svc_infra/api/fastapi/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
88
88
  svc_infra/api/fastapi/openapi/apply.py,sha256=VAwRfcYSLCSKIpO1dp9okG1MXvkZuciU41jrSSuvUpI,1697
89
89
  svc_infra/api/fastapi/openapi/conventions.py,sha256=e6gUsFyfEGvz3KkUimjAWMfF7_fonMJ3IoGvQZjpvfs,7171
90
90
  svc_infra/api/fastapi/openapi/models.py,sha256=MmXMpPZQjZygajfIHaL4_3q0zluPtpRevsSyOIiE83Y,2562
91
- svc_infra/api/fastapi/openapi/mutators.py,sha256=0bSKVPw-lDA3L_vaPDhcM0rC7xKS-XNTzgR8hxxoM18,55266
91
+ svc_infra/api/fastapi/openapi/mutators.py,sha256=gzd7WbS_t6zrmFBJ_m1sxpzRNK6g_foHS5TqOED79Qw,55414
92
92
  svc_infra/api/fastapi/openapi/pipeline.py,sha256=GAf-qzwmWlYbrAlPirr8w89fEO4-kFrhCoeMj-7mE44,646
93
93
  svc_infra/api/fastapi/openapi/responses.py,sha256=pBUoJd0lltBkQBJACS1Zd1wd975gbw6dYyMEqyueRuw,1093
94
94
  svc_infra/api/fastapi/openapi/security.py,sha256=U78KMwgc7FilFPLbIE2f6xrp74hq6TDFXpUMGRyK_bg,1248
95
- svc_infra/api/fastapi/ops/add.py,sha256=39puYLuJdZuIBkpmMiHF6N8H4D-96TRtFdYidbzqndI,2311
95
+ svc_infra/api/fastapi/ops/add.py,sha256=ILMzFhYhfAaHEk6iqZQ15a2xgKAiZ0IKNuqWX-T8F04,2560
96
96
  svc_infra/api/fastapi/pagination.py,sha256=gQobCBb1dmyRjm62xYfkz-rBq_0hNyD0qEigYaLUky8,10972
97
97
  svc_infra/api/fastapi/paths/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  svc_infra/api/fastapi/paths/auth.py,sha256=hy9N0QFQnpv7dBOuHStui5eP9oIGHrawa0sADIVVD64,553
@@ -252,7 +252,7 @@ svc_infra/jobs/builtins/outbox_processor.py,sha256=VZoehNyjdaV_MmV74WMcbZR6z9E3V
252
252
  svc_infra/jobs/builtins/webhook_delivery.py,sha256=z_cl6YKwnduGjGaB8ZoUpKhFcEAhUZqqBma8v2FO1so,2982
253
253
  svc_infra/jobs/easy.py,sha256=eix-OxWeE3vdkY3GGNoYM0GAyOxc928SpiSzMkr9k0A,977
254
254
  svc_infra/jobs/loader.py,sha256=LFO6gOacj6rT698vkDg0YfcHDRTue4zus3Nl9QrS5R0,1164
255
- svc_infra/jobs/queue.py,sha256=PS5f4CJm5_K7icojTxZOwC6uKw3O2M-jE111u85ySbA,2288
255
+ svc_infra/jobs/queue.py,sha256=KNpYU_za8B7mmmWY6eWDohSRYy7VIKHyWAGD1qkXUOw,2816
256
256
  svc_infra/jobs/redis_queue.py,sha256=wgmWKslF1dkYscJe49UgUX7gwEuGyOUWEb0-pn82I3g,7543
257
257
  svc_infra/jobs/scheduler.py,sha256=dTUEEyEuTVHNmJT8wPdMu4YjnTN7R_YW67gtCKpqC7M,1180
258
258
  svc_infra/jobs/worker.py,sha256=T2A575_mnieJHPOYU_FseubLA_HQf9pB4CkRgzRJBHU,694
@@ -323,9 +323,9 @@ svc_infra/webhooks/__init__.py,sha256=fvPhbFoS6whoT67DWp43pL3m1o-et104vwqxunCUAP
323
323
  svc_infra/webhooks/add.py,sha256=u9Spfwg0ztQmXg7uXP1sZ9-_qwnagnW4UnV9HvQtPwc,12191
324
324
  svc_infra/webhooks/fastapi.py,sha256=BCNvGNxukf6dC2a4i-6en-PrjBGV19YvCWOot5lXWsA,1101
325
325
  svc_infra/webhooks/router.py,sha256=6JvAVPMEth_xxHX-IsIOcyMgHX7g1H0OVxVXKLuMp9w,1596
326
- svc_infra/webhooks/service.py,sha256=hWgiJRXKBwKunJOx91C7EcLUkotDtD3Xp0RT6vj2IC0,1797
326
+ svc_infra/webhooks/service.py,sha256=hh-rw0otc00vipZ998XaV5mHsk0IDGYqon0FnhaGr60,2229
327
327
  svc_infra/webhooks/signing.py,sha256=NCwdZzmravUe7HVIK_uXK0qqf12FG-_MVsgPvOw6lsM,784
328
- svc_infra-0.1.626.dist-info/METADATA,sha256=OC4P-US0KYQFiLZZflkU5ndhPHcj4F0RhM1MxK0PBK0,8748
329
- svc_infra-0.1.626.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
330
- svc_infra-0.1.626.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
331
- svc_infra-0.1.626.dist-info/RECORD,,
328
+ svc_infra-0.1.628.dist-info/METADATA,sha256=gNFtYm9xyvUdLV7Llw0LCrfXZ3CADY9C_TQQ53RlBn8,8748
329
+ svc_infra-0.1.628.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
330
+ svc_infra-0.1.628.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
331
+ svc_infra-0.1.628.dist-info/RECORD,,