fastapi-reloader 1.3.2__py2.py3-none-any.whl → 1.3.4__py2.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.
fastapi_reloader/core.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from asyncio import Queue, ensure_future, sleep
2
2
  from collections import defaultdict
3
3
  from itertools import count
4
- from pathlib import Path
5
4
  from typing import Literal
6
5
 
7
6
  from fastapi import APIRouter, Response
@@ -22,26 +21,15 @@ def send_reload_signal():
22
21
  reload_router = APIRouter(prefix="/---fastapi-reloader---", tags=["hmr"])
23
22
 
24
23
 
25
- runtime_js = Path(__file__, "../runtime.js").resolve().read_text()
26
-
27
-
28
- def get_js():
29
- return runtime_js.replace("/0", f"/{get_id()}")
30
-
31
-
32
24
  @reload_router.head("")
33
25
  async def heartbeat():
34
- return Response(status_code=200)
35
-
36
-
37
- @reload_router.get("/poller.js")
38
- async def get_poller_js():
39
- return Response(get_js(), media_type="application/javascript")
26
+ return Response(status_code=202)
40
27
 
41
28
 
42
- @reload_router.get("/{key:int}")
43
- async def simple_refresh_trigger(key: int):
29
+ @reload_router.get("")
30
+ async def subscribe():
44
31
  async def event_generator():
32
+ key = get_id()
45
33
  queue = Queue[Literal[0, 1]]()
46
34
 
47
35
  stopped = False
@@ -66,4 +54,4 @@ async def simple_refresh_trigger(key: int):
66
54
  heartbeat_future.cancel()
67
55
  requests[key].remove(queue)
68
56
 
69
- return StreamingResponse(event_generator(), media_type="text/plain")
57
+ return StreamingResponse(event_generator(), 201, media_type="text/plain")
@@ -1,7 +1,10 @@
1
1
  from collections.abc import Awaitable, Callable
2
2
  from contextlib import asynccontextmanager
3
3
  from copy import copy
4
+ from inspect import ismethod
4
5
  from math import inf
6
+ from pathlib import Path
7
+ from types import MethodType
5
8
  from typing import Generic, TypeGuard, TypeVar
6
9
 
7
10
  from asgi_lifespan import LifespanManager
@@ -20,12 +23,19 @@ def is_streaming_response(response: Response) -> TypeGuard[StreamingResponse]:
20
23
  return hasattr(response, "body_iterator")
21
24
 
22
25
 
26
+ INJECTION = f"\n\n<script>\n{Path(__file__, '../runtime.js').resolve().read_text()}\n</script>".encode()
27
+
28
+ FLAG = " fastapi-reloader-injected " # to avoid double injection
29
+
30
+
23
31
  async def _injection_http_middleware(request: Request, call_next: Callable[[Request], Awaitable[Response]]):
24
32
  res = await call_next(request)
25
33
 
26
- if request.method != "GET" or "html" not in (res.headers.get("content-type", "")) or res.headers.get("content-encoding", "identity") != "identity":
34
+ if request.scope.get(FLAG) or request.method != "GET" or "html" not in (res.headers.get("content-type", "")) or res.headers.get("content-encoding", "identity") != "identity":
27
35
  return res
28
36
 
37
+ request.scope[FLAG] = True
38
+
29
39
  async def response():
30
40
  if is_streaming_response(res):
31
41
  async for chunk in res.body_iterator:
@@ -33,7 +43,7 @@ async def _injection_http_middleware(request: Request, call_next: Callable[[Requ
33
43
  else:
34
44
  yield res.body
35
45
 
36
- yield b'\n\n <script src="/---fastapi-reloader---/poller.js"></script>'
46
+ yield INJECTION
37
47
 
38
48
  headers = {k: v for k, v in res.headers.items() if k.lower() not in {"content-length", "transfer-encoding"}}
39
49
 
@@ -79,6 +89,14 @@ def patch_for_auto_reloading(app: ASGIApp): # this function is preserved for ba
79
89
  if isinstance(app, Starlette): # both FastAPI and Starlette have user_middleware attribute
80
90
  new_app = copy(app)
81
91
  new_app.user_middleware = [*app.user_middleware, html_injection_middleware] # before compression middlewares
92
+
93
+ # OTEL patches the app's build_middleware_stack method and keep a reference to the original build_middleware_stack.
94
+ # But both methods are bound to the original app instance, so we need to rebind them to the new app instance.
95
+ # The following loop generically rebinds all these methods, preventing potential issues caused by similar patches.
96
+ for i in dir(new_app):
97
+ if ismethod(method := getattr(new_app, i)) and method.__self__ is app:
98
+ setattr(new_app, i, MethodType(method.__func__, new_app))
99
+
82
100
  return _wrap_asgi_app(new_app)
83
101
 
84
102
  new_app = _wrap_asgi_app(app)
@@ -11,23 +11,37 @@ async function poll() {
11
11
  }
12
12
  }
13
13
 
14
- async function main() {
15
- const response = await fetch("/---fastapi-reloader---/0");
14
+ /** @param {Response} response */
15
+ async function wait(response) {
16
16
  const reader = response.body.getReader();
17
17
  const decoder = new TextDecoder();
18
18
  while (true) {
19
19
  const { done, value } = await reader.read();
20
20
  if (done) {
21
- break;
21
+ return;
22
22
  }
23
23
  if (value) {
24
24
  const chunk = decoder.decode(value, { stream: true });
25
25
  if (chunk.includes("1")) {
26
- await poll();
27
- location.reload();
26
+ return;
28
27
  }
29
28
  }
30
29
  }
31
30
  }
32
31
 
33
- main();
32
+ async function main() {
33
+ const response = await fetch("/---fastapi-reloader---").catch(() => null);
34
+ if (response?.ok && response.body) {
35
+ await wait(response).catch(() => null);
36
+ await poll();
37
+ location.reload();
38
+ } else {
39
+ await poll();
40
+ return await main();
41
+ }
42
+ }
43
+
44
+ if (!window.__fastapi_reloader_loaded) {
45
+ window.__fastapi_reloader_loaded = true;
46
+ main();
47
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-reloader
3
- Version: 1.3.2
3
+ Version: 1.3.4
4
4
  Project-URL: Homepage, https://github.com/promplate/hmr
5
5
  Requires-Dist: asgi-lifespan~=2.0
6
6
  Requires-Dist: fastapi~=0.115
@@ -74,6 +74,8 @@ app = html_injection_middleware(app)
74
74
  app.user_middleware.append(html_injection_middleware)
75
75
  ```
76
76
 
77
+ > It's safe to add `html_injection_middleware` in multiple places, even if their scopes overlap. We have safeguards in place to prevent double injection on both server and client side.
78
+
77
79
  The `auto_refresh_middleware` is a convenient wrapper that applies both `reloader_route_middleware` and `html_injection_middleware`. However, you can add them separately for more control:
78
80
 
79
81
  - **Fine-grained control**: If a sub-router in your application uses compression, you must add `html_injection_middleware` before the compression middleware on that router.
@@ -0,0 +1,8 @@
1
+ fastapi_reloader-1.3.4.dist-info/METADATA,sha256=7h8S-serIhPPn1UnyUCuIJOA6NgOI-I6oUDQ9mu-xOs,6500
2
+ fastapi_reloader-1.3.4.dist-info/WHEEL,sha256=pz1FfwQ2kf9tI4G8U2ObRTKdvsTSmrreuBTtdnO8pJw,94
3
+ fastapi_reloader-1.3.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ fastapi_reloader/__init__.py,sha256=qWjX076aoLEZxZoOIvtmg84khic2FBXpAWuRWVbouTY,309
5
+ fastapi_reloader/core.py,sha256=cqB9uVdPa-0SMafaFH2Ho7m8npD1u503JIGX43oZ4es,1485
6
+ fastapi_reloader/patcher.py,sha256=KJjjbT0uDjZv1Q95slW3MBoUggPrMdCamjnlTy-iTKc,4133
7
+ fastapi_reloader/runtime.js,sha256=kG73Qx2civNzrG36Hfsu9U_esmjYh4EqynpVcmUqB8o,1036
8
+ fastapi_reloader-1.3.4.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- fastapi_reloader-1.3.2.dist-info/METADATA,sha256=D7-0CWUurSwKz-MdsPAQL-Tm4SE6Lg0X5V_3q3I2qSQ,6314
2
- fastapi_reloader-1.3.2.dist-info/WHEEL,sha256=pz1FfwQ2kf9tI4G8U2ObRTKdvsTSmrreuBTtdnO8pJw,94
3
- fastapi_reloader-1.3.2.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- fastapi_reloader/__init__.py,sha256=qWjX076aoLEZxZoOIvtmg84khic2FBXpAWuRWVbouTY,309
5
- fastapi_reloader/core.py,sha256=pug78QfiDLaX7uBj9zbjAwphMlsFGlam0qEpUnHOhfo,1779
6
- fastapi_reloader/patcher.py,sha256=MXZjva7zIdSYN4j5RGxBMy2UtJ34pSj3ylwzcKfoAKU,3318
7
- fastapi_reloader/runtime.js,sha256=pUPeMnMuKGHhBTudp6bFuNTTyZhGSvrvvWy_lWVN8nA,713
8
- fastapi_reloader-1.3.2.dist-info/RECORD,,