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

@@ -27,6 +27,10 @@ class SimpleRateLimitMiddleware(BaseHTTPMiddleware):
27
27
  limit_resolver=None,
28
28
  # If True, automatically scopes the bucket key by tenant id when available
29
29
  scope_by_tenant: bool = False,
30
+ # When True, allows unresolved tenant IDs to fall back to an "X-Tenant-Id" header value.
31
+ # Disabled by default to avoid trusting arbitrary client-provided headers which could
32
+ # otherwise be used to evade per-tenant limits when authentication fails.
33
+ allow_untrusted_tenant_header: bool = False,
30
34
  store: RateLimitStore | None = None,
31
35
  ):
32
36
  super().__init__(app)
@@ -34,6 +38,7 @@ class SimpleRateLimitMiddleware(BaseHTTPMiddleware):
34
38
  self.key_fn = key_fn or (lambda r: r.headers.get("X-API-Key") or r.client.host)
35
39
  self._limit_resolver = limit_resolver
36
40
  self.scope_by_tenant = scope_by_tenant
41
+ self._allow_untrusted_tenant_header = allow_untrusted_tenant_header
37
42
  self.store = store or InMemoryRateLimitStore(limit=limit)
38
43
 
39
44
  async def dispatch(self, request, call_next):
@@ -45,6 +50,9 @@ class SimpleRateLimitMiddleware(BaseHTTPMiddleware):
45
50
  tenant_id = await _resolve_tenant_id(request)
46
51
  except Exception:
47
52
  tenant_id = None
53
+ # Fallback: read from header only if explicitly trusted
54
+ if not tenant_id and self._allow_untrusted_tenant_header:
55
+ tenant_id = request.headers.get("X-Tenant-Id") or request.headers.get("X-Tenant-ID")
48
56
 
49
57
  key = self.key_fn(request)
50
58
  if self.scope_by_tenant and tenant_id:
@@ -21,17 +21,8 @@ def _discover_fs_topics(docs_dir: Path) -> Dict[str, Path]:
21
21
 
22
22
 
23
23
  def _discover_pkg_topics() -> Dict[str, object]:
24
- topics: Dict[str, object] = {}
25
- try:
26
- import importlib.resources as ir
27
-
28
- pkg_docs = ir.files("svc_infra.bundled_docs")
29
- for res in pkg_docs.iterdir():
30
- if res.name.endswith(".md"):
31
- topics[Path(res.name).stem.replace(" ", "-")] = res
32
- except Exception:
33
- pass
34
- return topics
24
+ # No bundled fallback; docs are packaged from repo root 'docs/'
25
+ return {}
35
26
 
36
27
 
37
28
  def _resolve_docs_dir(ctx: click.Context) -> Path | None:
@@ -65,9 +56,7 @@ class DocsGroup(TyperGroup):
65
56
  names: List[str] = list(super().list_commands(ctx) or [])
66
57
  dir_to_use = _resolve_docs_dir(ctx)
67
58
  fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
68
- pkg = _discover_pkg_topics() if not fs else {}
69
59
  names.extend(fs.keys())
70
- names.extend(pkg.keys())
71
60
  # Deduplicate and sort
72
61
  uniq = sorted({*names})
73
62
  return uniq
@@ -90,27 +79,7 @@ class DocsGroup(TyperGroup):
90
79
 
91
80
  return _show_fs
92
81
 
93
- if not fs:
94
- pkg = _discover_pkg_topics()
95
- if name in pkg:
96
- res = pkg[name]
97
-
98
- @click.command(name=name)
99
- def _show_pkg() -> None:
100
- try:
101
- import importlib.resources as ir
102
-
103
- content = getattr(res, "read_text", None)
104
- if callable(content):
105
- text = content(encoding="utf-8", errors="replace")
106
- else:
107
- with ir.as_file(res) as p:
108
- text = Path(p).read_text(encoding="utf-8", errors="replace")
109
- click.echo(text)
110
- except Exception as e: # pragma: no cover
111
- raise click.ClickException(f"Failed to load bundled doc: {e}")
112
-
113
- return _show_pkg
82
+ # No packaged fallback
114
83
 
115
84
  return None
116
85
 
@@ -137,23 +106,6 @@ def register(app: typer.Typer) -> None:
137
106
  if topic in fs:
138
107
  typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
139
108
  raise typer.Exit(code=0)
140
- if not fs:
141
- pkg = _discover_pkg_topics()
142
- if topic in pkg:
143
- try:
144
- import importlib.resources as ir
145
-
146
- res = pkg[topic]
147
- content = getattr(res, "read_text", None)
148
- if callable(content):
149
- text = content(encoding="utf-8", errors="replace")
150
- else:
151
- with ir.as_file(res) as p:
152
- text = Path(p).read_text(encoding="utf-8", errors="replace")
153
- typer.echo(text)
154
- raise typer.Exit(code=0)
155
- except Exception as e: # pragma: no cover
156
- raise typer.BadParameter(f"Failed to load bundled topic '{topic}': {e}")
157
109
  raise typer.BadParameter(f"Unknown topic: {topic}")
158
110
 
159
111
  @docs_app.command("list", help="List available documentation topics")
@@ -162,15 +114,12 @@ def register(app: typer.Typer) -> None:
162
114
  root = resolve_project_root()
163
115
  dir_to_use = _resolve_docs_dir(ctx)
164
116
  fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
165
- pkg = _discover_pkg_topics() if not fs else {}
166
117
  for name, path in fs.items():
167
118
  try:
168
119
  rel = path.relative_to(root)
169
120
  typer.echo(f"{name}\t{rel}")
170
121
  except Exception:
171
122
  typer.echo(f"{name}\t{path}")
172
- for name in sorted(pkg.keys()):
173
- typer.echo(f"{name}\t(bundled)")
174
123
 
175
124
  # Also support a generic "show" command
176
125
  @docs_app.command("show", help="Show docs for a topic (alternative to dynamic subcommand)")
@@ -181,23 +130,6 @@ def register(app: typer.Typer) -> None:
181
130
  if topic in fs:
182
131
  typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
183
132
  return
184
- if not fs:
185
- pkg = _discover_pkg_topics()
186
- if topic in pkg:
187
- try:
188
- import importlib.resources as ir
189
-
190
- res = pkg[topic]
191
- content = getattr(res, "read_text", None)
192
- if callable(content):
193
- text = content(encoding="utf-8", errors="replace")
194
- else:
195
- with ir.as_file(res) as p:
196
- text = Path(p).read_text(encoding="utf-8", errors="replace")
197
- typer.echo(text)
198
- return
199
- except Exception as e: # pragma: no cover
200
- raise typer.BadParameter(f"Failed to load bundled topic '{topic}': {e}")
201
133
  raise typer.BadParameter(f"Unknown topic: {topic}")
202
134
 
203
135
  app.add_typer(docs_app, name="docs")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: svc-infra
3
- Version: 0.1.616
3
+ Version: 0.1.617
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
@@ -80,7 +80,7 @@ svc_infra/api/fastapi/middleware/errors/handlers.py,sha256=pQMVs5n627vcKkDFEaUzx
80
80
  svc_infra/api/fastapi/middleware/idempotency.py,sha256=vnBQgMWzJVaF8oWgfw2ATjEKCyQifDeGPUc9z1N7ebE,5051
81
81
  svc_infra/api/fastapi/middleware/idempotency_store.py,sha256=BQN_Cq_jf_cuZRhze4EF5v0lOMQXpUWoRo7CsSTprug,5528
82
82
  svc_infra/api/fastapi/middleware/optimistic_lock.py,sha256=9lOMBI4VNIVndXnrMmgSq4qeR7xPjNR1H9d1F71M5S8,1271
83
- svc_infra/api/fastapi/middleware/ratelimit.py,sha256=8A_J6JdU-cs9QYqP6Ufbp4vDkiH-H6CsZwege1nqf24,3855
83
+ svc_infra/api/fastapi/middleware/ratelimit.py,sha256=f-nvsh3wqLMpDEmwPsKvVnOrM3cfC9AqQyl8eUqrUQM,4496
84
84
  svc_infra/api/fastapi/middleware/ratelimit_store.py,sha256=LmJR8-kkW42rzOjls9lG1SBtCKjVY7L2Y_bNKHNY3-A,2553
85
85
  svc_infra/api/fastapi/middleware/request_id.py,sha256=Iru7ypTdK_n76lwziEGDWoVF4FKS0Ps1PMASYmzK8ek,768
86
86
  svc_infra/api/fastapi/middleware/request_size_limit.py,sha256=AcGqaB-F7Tbhg-at7ViT4Bpifst34jFneDBlUBjgo5I,1248
@@ -142,7 +142,7 @@ svc_infra/cli/cmds/db/sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
142
142
  svc_infra/cli/cmds/db/sql/alembic_cmds.py,sha256=kkAu8sfBLWbb9ApMS95b7b_c6GifqvPaRsO7K8icMVI,9649
143
143
  svc_infra/cli/cmds/db/sql/sql_export_cmds.py,sha256=6MxoQO-9upoXg0cl1RHIqz96yXFVGidiBYp_ewhB0E0,2700
144
144
  svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py,sha256=eNTCqHXOxgl9H3WTbGVn9BHXYwCpjIEJsDqhEFdrYMM,4613
145
- svc_infra/cli/cmds/docs/docs_cmds.py,sha256=uRoClC68-FRLGkHyTYds3h2xKE_4STzkudB6T72DiKo,7611
145
+ svc_infra/cli/cmds/docs/docs_cmds.py,sha256=A3LT__UffM8elPGTnaXmjNqsq8h4y2VVMMSzoqAeZeM,4668
146
146
  svc_infra/cli/cmds/dx/__init__.py,sha256=wQtl3-kOgoESlpVkjl3YFtqkOnQSIvVsOdutiaZFejM,197
147
147
  svc_infra/cli/cmds/dx/dx_cmds.py,sha256=XTKUJzS3UIYn6h3CHzDEWKYJaWn0TzGiUCq3OeW27E0,3326
148
148
  svc_infra/cli/cmds/help.py,sha256=wGfZFMYaR2ZPwW2JwKDU7M3m4AtdCd8GRQ412AmEBUM,758
@@ -296,7 +296,7 @@ svc_infra/webhooks/fastapi.py,sha256=BCNvGNxukf6dC2a4i-6en-PrjBGV19YvCWOot5lXWsA
296
296
  svc_infra/webhooks/router.py,sha256=6JvAVPMEth_xxHX-IsIOcyMgHX7g1H0OVxVXKLuMp9w,1596
297
297
  svc_infra/webhooks/service.py,sha256=hWgiJRXKBwKunJOx91C7EcLUkotDtD3Xp0RT6vj2IC0,1797
298
298
  svc_infra/webhooks/signing.py,sha256=NCwdZzmravUe7HVIK_uXK0qqf12FG-_MVsgPvOw6lsM,784
299
- svc_infra-0.1.616.dist-info/METADATA,sha256=8UIJLzGOQ3LgK55oyA9V_dGAg_i33C5wou1tkDajCzM,8106
300
- svc_infra-0.1.616.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
301
- svc_infra-0.1.616.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
302
- svc_infra-0.1.616.dist-info/RECORD,,
299
+ svc_infra-0.1.617.dist-info/METADATA,sha256=gFtkUXgTuAGcECWRsaWvXwoxnTEqxyl2yTYw9zzYhSk,8106
300
+ svc_infra-0.1.617.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
301
+ svc_infra-0.1.617.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
302
+ svc_infra-0.1.617.dist-info/RECORD,,