fastapi-reloader 1.0__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.
@@ -0,0 +1,4 @@
1
+ from .core import send_reload_signal
2
+ from .patcher import patch_for_auto_reloading
3
+
4
+ __all__ = ["patch_for_auto_reloading", "send_reload_signal"]
@@ -0,0 +1,63 @@
1
+ from asyncio import Queue, ensure_future, sleep
2
+ from collections import defaultdict
3
+ from itertools import count
4
+ from pathlib import Path
5
+ from typing import Literal
6
+
7
+ from fastapi import APIRouter, Response
8
+ from fastapi.responses import StreamingResponse
9
+
10
+ get_id = count().__next__
11
+
12
+ requests: dict[int, list[Queue[Literal[0, 1]]]] = defaultdict(list)
13
+
14
+
15
+ def send_reload_signal():
16
+ for subscribers in requests.values():
17
+ for queue in subscribers:
18
+ queue.put_nowait(1)
19
+
20
+
21
+ hmr_router = APIRouter(prefix="/---fastapi-reloader---", tags=["hmr"])
22
+
23
+
24
+ runtime_js = Path(__file__, "../runtime.js").read_text()
25
+
26
+
27
+ def get_js():
28
+ return runtime_js.replace("/0", f"/{get_id()}")
29
+
30
+
31
+ @hmr_router.head("")
32
+ async def heartbeat():
33
+ return Response(status_code=200)
34
+
35
+
36
+ @hmr_router.get("/{key:int}")
37
+ async def simple_refresh_trigger(key: int):
38
+ async def event_generator():
39
+ queue = Queue[Literal[0, 1]]()
40
+
41
+ stopped = False
42
+
43
+ async def heartbeat():
44
+ while not stopped:
45
+ queue.put_nowait(0)
46
+ await sleep(1)
47
+
48
+ requests[key].append(queue)
49
+
50
+ heartbeat_future = ensure_future(heartbeat())
51
+
52
+ try:
53
+ yield "0\n"
54
+ while True:
55
+ value = await queue.get()
56
+ yield f"{value}\n"
57
+ if value == 1:
58
+ break
59
+ finally:
60
+ heartbeat_future.cancel()
61
+ requests[key].remove(queue)
62
+
63
+ return StreamingResponse(event_generator(), media_type="text/plain")
@@ -0,0 +1,43 @@
1
+ from collections.abc import Awaitable, Callable
2
+ from typing import TypeGuard
3
+
4
+ from fastapi import FastAPI, Request, Response
5
+ from fastapi.responses import StreamingResponse
6
+ from starlette.middleware import Middleware
7
+ from starlette.middleware.base import BaseHTTPMiddleware
8
+ from starlette.types import ASGIApp
9
+
10
+ from .core import get_js, hmr_router
11
+
12
+
13
+ def is_streaming_response(response: Response) -> TypeGuard[StreamingResponse]:
14
+ # In fact, it may not be a fastapi's StreamingResponse, but a starlette's one with the same interface
15
+ return hasattr(response, "body_iterator")
16
+
17
+
18
+ def patch_for_auto_reloading(app: ASGIApp):
19
+ new_app = FastAPI(openapi_url=None)
20
+ new_app.include_router(hmr_router)
21
+ new_app.mount("/", app)
22
+
23
+ async def hmr_middleware(request: Request, call_next: Callable[[Request], Awaitable[Response]]):
24
+ res = await call_next(request)
25
+
26
+ if request.method != "GET" or "html" not in (res.headers.get("content-type", "")):
27
+ return res
28
+
29
+ async def response():
30
+ if is_streaming_response(res):
31
+ async for chunk in res.body_iterator:
32
+ yield chunk
33
+ else:
34
+ yield res.body
35
+ yield f"\n\n <script> {get_js()} </script>".encode()
36
+
37
+ headers = {k: v for k, v in res.headers.items() if k.lower() not in {"content-length", "content-encoding", "transfer-encoding"}}
38
+
39
+ return StreamingResponse(response(), res.status_code, headers, res.media_type)
40
+
41
+ new_app.user_middleware.append(Middleware(BaseHTTPMiddleware, dispatch=hmr_middleware)) # the last middleware is the first one to be called
42
+
43
+ return new_app
@@ -0,0 +1,27 @@
1
+ (async function () {
2
+ const response = await fetch("/---fastapi-reloader---/0");
3
+ const reader = response.body.getReader();
4
+ const decoder = new TextDecoder();
5
+ while (true) {
6
+ const { done, value } = await reader.read();
7
+ if (done) {
8
+ break;
9
+ }
10
+ if (value) {
11
+ const chunk = decoder.decode(value, { stream: true });
12
+ if (chunk.includes("1")) {
13
+ while (true) {
14
+ try {
15
+ const res = await fetch("/---fastapi-reloader---", {
16
+ method: "HEAD",
17
+ });
18
+ if (res.ok) {
19
+ break;
20
+ }
21
+ } catch (error) {}
22
+ }
23
+ location.reload();
24
+ }
25
+ }
26
+ }
27
+ })();
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.1
2
+ Name: fastapi-reloader
3
+ Version: 1.0
4
+ Project-URL: Homepage, https://github.com/promplate/hmr
5
+ Requires-Dist: fastapi~=0.115
6
+ Requires-Dist: hmr~=0.4.0
7
+ Description-Content-Type: text/markdown
8
+
9
+ # FastAPI Reloader
10
+
11
+ [![PyPI - Version](https://img.shields.io/pypi/v/fastapi-reloader)](https://pypi.org/project/fastapi-reloader/)
12
+ [![PyPI - Downloads](https://img.shields.io/pypi/dw/fastapi-reloader)](https://pepy.tech/projects/fastapi-reloader/)
13
+
14
+ A lightweight middleware ASGI applications that enables automatic browser page reloading during development.
15
+
16
+ ## Features
17
+
18
+ - 🔄 Automatic browser refresh when code changes (work with `uvicorn-hmr`)
19
+ - 🚀 Works with any ASGI application
20
+ - 🔌 Simple integration with just two function calls
21
+
22
+ ## Installation
23
+
24
+ ```sh
25
+ pip install fastapi-reloader
26
+ ```
27
+
28
+ For a more comprehensive development experience, consider using `uvicorn-hmr` which includes this package:
29
+
30
+ ```sh
31
+ pip install uvicorn-hmr[all]
32
+ ```
33
+
34
+ Then run your app with:
35
+
36
+ ```sh
37
+ uvicorn-hmr main:app --reload
38
+ ```
39
+
40
+ ## Advanced Usage
41
+
42
+ ### Manual Integration
43
+
44
+ ```python
45
+ from fastapi import FastAPI
46
+ from fastapi_reloader import patch_for_auto_reloading
47
+
48
+ app = FastAPI() # or some other ASGI app
49
+
50
+ app = patch_for_auto_reloading(app) # this will return a new FastAPI app
51
+ ```
52
+
53
+ ### Manual Trigger
54
+
55
+ You can manually trigger a reload from your code:
56
+
57
+ ```python
58
+ from fastapi_reloader import send_reload_signal
59
+
60
+ send_reload_signal() # When you need to trigger a reload
61
+ ```
62
+
63
+ ## How It Works
64
+
65
+ The package injects a small JavaScript snippet into your HTML responses that:
66
+
67
+ 1. Opens a long-lived connection to the server
68
+ 2. Listens for reload signals
69
+ 3. Start polling for heartbeat when `send_reload_signal` is called
70
+ 4. Reloads the page when heartbeat from new server is received
71
+
72
+ ## Configuration
73
+
74
+ The package works out-of-the-box with default settings. No additional configuration is required.
75
+
76
+ ## Limitations
77
+
78
+ - Unlike `uvicorn-hmr`, which does on-demand fine-grained reloading on the server side, this package simply reloads all the pages in the browser.
79
+ - Designed for development use only (not for production)
80
+ - Requires JavaScript to be enabled in the browser
81
+
82
+ ## Contributing
83
+
84
+ Contributions are welcome! Please open an issue or submit a pull request.
@@ -0,0 +1,8 @@
1
+ fastapi_reloader-1.0.dist-info/METADATA,sha256=Jwsu7h94NyFlc-8qTGKmuHzJE2t-zG-9-SK5K6DuNKM,2260
2
+ fastapi_reloader-1.0.dist-info/WHEEL,sha256=EIrqRoDPs10x40oLVL4n4Pk5qQrfAB4xusd3Yd1GnUQ,94
3
+ fastapi_reloader-1.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ fastapi_reloader/__init__.py,sha256=VE5m-0M92Weme8COqLs0LwIx5g6FHhqyOLGAgY6Zkzs,145
5
+ fastapi_reloader/core.py,sha256=1U4CFQhu0I8Y8HqRfZBzI71WthGUF4D7MHIxp8_hg6U,1528
6
+ fastapi_reloader/patcher.py,sha256=d2wG3eD6Hs0URBhJag0uMeYq-QQP_YJZXOkOCGllp7s,1656
7
+ fastapi_reloader/runtime.js,sha256=ltpmyOLzDaUjjb2WcEldWpgyh69gvi4nCaRnTDjT1jc,676
8
+ fastapi_reloader-1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-backend (2.4.4)
3
+ Root-Is-Purelib: true
4
+ Tag: py2.py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+
3
+ [gui_scripts]
4
+