fastapi-reloader 1.3.1__py2.py3-none-any.whl → 1.3.2__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/__init__.py +2 -2
- fastapi_reloader/core.py +1 -0
- fastapi_reloader/patcher.py +31 -8
- fastapi_reloader-1.3.2.dist-info/METADATA +160 -0
- fastapi_reloader-1.3.2.dist-info/RECORD +8 -0
- fastapi_reloader-1.3.1.dist-info/METADATA +0 -84
- fastapi_reloader-1.3.1.dist-info/RECORD +0 -8
- {fastapi_reloader-1.3.1.dist-info → fastapi_reloader-1.3.2.dist-info}/WHEEL +0 -0
- {fastapi_reloader-1.3.1.dist-info → fastapi_reloader-1.3.2.dist-info}/entry_points.txt +0 -0
fastapi_reloader/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
from .core import send_reload_signal
|
2
|
-
from .patcher import patch_for_auto_reloading
|
2
|
+
from .patcher import auto_refresh_middleware, html_injection_middleware, patch_for_auto_reloading, reloader_route_middleware
|
3
3
|
|
4
|
-
__all__ = ["patch_for_auto_reloading", "send_reload_signal"]
|
4
|
+
__all__ = ["auto_refresh_middleware", "html_injection_middleware", "patch_for_auto_reloading", "reloader_route_middleware", "send_reload_signal"]
|
fastapi_reloader/core.py
CHANGED
@@ -13,6 +13,7 @@ requests: dict[int, list[Queue[Literal[0, 1]]]] = defaultdict(list)
|
|
13
13
|
|
14
14
|
|
15
15
|
def send_reload_signal():
|
16
|
+
"""Broadcast a reload signal to all connected clients and break their long-polling connections."""
|
16
17
|
for subscribers in requests.values():
|
17
18
|
for queue in subscribers:
|
18
19
|
queue.put_nowait(1)
|
fastapi_reloader/patcher.py
CHANGED
@@ -2,7 +2,7 @@ from collections.abc import Awaitable, Callable
|
|
2
2
|
from contextlib import asynccontextmanager
|
3
3
|
from copy import copy
|
4
4
|
from math import inf
|
5
|
-
from typing import TypeGuard
|
5
|
+
from typing import Generic, TypeGuard, TypeVar
|
6
6
|
|
7
7
|
from asgi_lifespan import LifespanManager
|
8
8
|
from fastapi import FastAPI, Request, Response
|
@@ -40,10 +40,25 @@ async def _injection_http_middleware(request: Request, call_next: Callable[[Requ
|
|
40
40
|
return StreamingResponse(response(), res.status_code, headers, res.media_type)
|
41
41
|
|
42
42
|
|
43
|
-
|
43
|
+
T = TypeVar("T", bound=ASGIApp)
|
44
44
|
|
45
45
|
|
46
|
-
|
46
|
+
class UniversalMiddleware(Middleware, Generic[T]):
|
47
|
+
"""Adapt an ASGI middleware so it can serve both Starlette/FastAPI middleware slots and plain ASGI usage."""
|
48
|
+
|
49
|
+
def __init__(self, asgi_middleware: Callable[[ASGIApp], T]):
|
50
|
+
self.fn = asgi_middleware
|
51
|
+
super().__init__(self)
|
52
|
+
|
53
|
+
def __call__(self, app):
|
54
|
+
return self.fn(app)
|
55
|
+
|
56
|
+
|
57
|
+
html_injection_middleware = UniversalMiddleware(lambda app: BaseHTTPMiddleware(app, _injection_http_middleware))
|
58
|
+
"""This middleware injects the HMR client script into HTML responses."""
|
59
|
+
|
60
|
+
|
61
|
+
def _wrap_asgi_app(app: ASGIApp):
|
47
62
|
@asynccontextmanager
|
48
63
|
async def lifespan(_):
|
49
64
|
async with LifespanManager(app, inf, inf):
|
@@ -56,13 +71,21 @@ def _reloader_middleware(app: ASGIApp):
|
|
56
71
|
return new_app
|
57
72
|
|
58
73
|
|
59
|
-
|
74
|
+
reloader_route_middleware = UniversalMiddleware(_wrap_asgi_app)
|
75
|
+
"""This middleware wraps the app with a FastAPI app that handles reload signals."""
|
76
|
+
|
77
|
+
|
78
|
+
def patch_for_auto_reloading(app: ASGIApp): # this function is preserved for backward compatibility
|
60
79
|
if isinstance(app, Starlette): # both FastAPI and Starlette have user_middleware attribute
|
61
80
|
new_app = copy(app)
|
62
|
-
new_app.user_middleware = [*app.user_middleware,
|
63
|
-
return
|
81
|
+
new_app.user_middleware = [*app.user_middleware, html_injection_middleware] # before compression middlewares
|
82
|
+
return _wrap_asgi_app(new_app)
|
64
83
|
|
65
|
-
new_app =
|
66
|
-
new_app.user_middleware.append(
|
84
|
+
new_app = _wrap_asgi_app(app)
|
85
|
+
new_app.user_middleware.append(html_injection_middleware) # the last middleware is the first one to be called
|
67
86
|
|
68
87
|
return new_app
|
88
|
+
|
89
|
+
|
90
|
+
auto_refresh_middleware = UniversalMiddleware(patch_for_auto_reloading)
|
91
|
+
"""This middleware combines the two middlewares above to enable the full functionality of this package."""
|
@@ -0,0 +1,160 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: fastapi-reloader
|
3
|
+
Version: 1.3.2
|
4
|
+
Project-URL: Homepage, https://github.com/promplate/hmr
|
5
|
+
Requires-Dist: asgi-lifespan~=2.0
|
6
|
+
Requires-Dist: fastapi~=0.115
|
7
|
+
Description-Content-Type: text/markdown
|
8
|
+
|
9
|
+
# FastAPI Reloader
|
10
|
+
|
11
|
+
[](https://pypi.org/project/fastapi-reloader/)
|
12
|
+
[](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 server restarts
|
19
|
+
- 🚀 Works with any ASGI application and any event loop
|
20
|
+
- 🔌 Simple integration with just two steps
|
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 --refresh
|
38
|
+
```
|
39
|
+
|
40
|
+
## Standalone Usage
|
41
|
+
|
42
|
+
You can also use `fastapi-reloader` as a standalone package without `uvicorn-hmr`. However, there are a few things to keep in mind.
|
43
|
+
|
44
|
+
It's important to understand the roles of the different packages:
|
45
|
+
|
46
|
+
- `uvicorn-hmr` Provides server-side hot module reloading (as a drop-in replacement for `uvicorn --reload`).
|
47
|
+
- `fastapi-reloader` Triggers a [browser page refresh](https://developer.mozilla.org/docs/Web/API/Location/reload "window.reload") when the server restarts.
|
48
|
+
|
49
|
+
If you configure `fastapi-reloader` manually and run your ASGI app with a standard reloader like `uvicorn --reload`, the behavior will be similar to `uvicorn-hmr --refresh` but with "cold" (process-restarting) server reloads instead of "hot" (in-process) reloads provided by [`hmr`](https://pyth-on-line.promplate.dev/hmr).
|
50
|
+
|
51
|
+
The manual integration steps are quite straightforward:
|
52
|
+
|
53
|
+
### Step 1: Add Middleware
|
54
|
+
|
55
|
+
`fastapi-reloader` works by injecting a `<script>` tag into HTML responses.
|
56
|
+
|
57
|
+
```python
|
58
|
+
from fastapi import FastAPI
|
59
|
+
from fastapi_reloader import auto_refresh_middleware
|
60
|
+
|
61
|
+
app = FastAPI()
|
62
|
+
app.add_middleware(auto_refresh_middleware)
|
63
|
+
```
|
64
|
+
|
65
|
+
Note that this middleware must be placed **before** any compression middleware (like Starlette's `GZipMiddleware`).
|
66
|
+
|
67
|
+
If you can't place it as early as possible in the middleware stack, you can use the following ways:
|
68
|
+
|
69
|
+
```python
|
70
|
+
# Approach 1: wrap your app with it (works with any ASGI app)
|
71
|
+
app = html_injection_middleware(app)
|
72
|
+
|
73
|
+
# Approach 2: manually add it to the user_middleware list (supported FastAPI, Starlette etc.)
|
74
|
+
app.user_middleware.append(html_injection_middleware)
|
75
|
+
```
|
76
|
+
|
77
|
+
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
|
+
|
79
|
+
- **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.
|
80
|
+
- **Scoped reloading**: If you only want to enable auto-reloading for a specific part of your app, you can apply `html_injection_middleware` only to that sub-router.
|
81
|
+
|
82
|
+
> The `reloader_route_middleware` mounts the necessary endpoints and should typically be added to the main application instance.
|
83
|
+
|
84
|
+
```python
|
85
|
+
from fastapi import FastAPI
|
86
|
+
from starlette.middleware.gzip import GZipMiddleware
|
87
|
+
from fastapi_reloader import html_injection_middleware, reloader_route_middleware
|
88
|
+
|
89
|
+
app = FastAPI()
|
90
|
+
# Apply the reloader routes to the main app
|
91
|
+
app.add_middleware(reloader_route_middleware)
|
92
|
+
|
93
|
+
# Apply HTML injection middleware before compression ones
|
94
|
+
app.add_middleware(html_injection_middleware)
|
95
|
+
app.add_middleware(GZipMiddleware) # or BrotliMiddleware, ZstMiddleware, etc.
|
96
|
+
```
|
97
|
+
|
98
|
+
FastAPI routers needs further configuration like this (because [`fastapi.APIRouter`](https://fastapi.tiangolo.com/reference/apirouter/) doesn't support middlewares directly):
|
99
|
+
|
100
|
+
```python
|
101
|
+
app.mount(router.prefix, html_injection_middleware(router)) # place this first to shadows the next line
|
102
|
+
app.include_router(router) # this can't be removed because FastAPI needs it to generate OpenAPI schema
|
103
|
+
```
|
104
|
+
|
105
|
+
### Step 2: Manually Triggering Reloads
|
106
|
+
|
107
|
+
When used standalone, you **have to** add a few lines into your code to manually *trigger the reload signal* (aka. call `send_reload_signal()`) before your ASGI server shuts down (I mean, when a server receives a shutdown signal like SIGINT/SIGTERM or it want to restart because of code changes etc.). `fastapi-reloader` works with any ASGI server, but most of them wait for open connections to close before shutting down. The long-polling connection internally used by `uvicorn-hmr` won't close on its own (because we have no idea of when it will happen), preventing the server from shutting down gracefully (behaving like a deadlock).
|
108
|
+
|
109
|
+
So you need to hook into your ASGI server's shutdown process to call `send_reload_signal()`. This can be done through subclassing, monkey-patching, or using a library like [`dowhen`](https://github.com/gaogaotiantian/dowhen).
|
110
|
+
|
111
|
+
Here is an example for `uvicorn`:
|
112
|
+
|
113
|
+
```python
|
114
|
+
from fastapi_reloader import send_reload_signal
|
115
|
+
import uvicorn
|
116
|
+
|
117
|
+
_shutdown = uvicorn.Server.shutdown
|
118
|
+
|
119
|
+
def shutdown(self, *args, **kwargs):
|
120
|
+
send_reload_signal()
|
121
|
+
return _shutdown(self, *args, **kwargs)
|
122
|
+
|
123
|
+
uvicorn.Server.shutdown = shutdown
|
124
|
+
```
|
125
|
+
|
126
|
+
and start the server as normal
|
127
|
+
|
128
|
+
```sh
|
129
|
+
uvicorn main:app --reload
|
130
|
+
```
|
131
|
+
|
132
|
+
or this way:
|
133
|
+
|
134
|
+
```python
|
135
|
+
if __name__ == "__main__":
|
136
|
+
uvicorn.run("main:app", reload=True)
|
137
|
+
```
|
138
|
+
|
139
|
+
## How It Works
|
140
|
+
|
141
|
+
The package injects a small JavaScript snippet into your HTML responses that:
|
142
|
+
|
143
|
+
1. Opens a long-lived connection to the server
|
144
|
+
2. Listens for reload signals
|
145
|
+
3. Starts polling for heartbeat when `send_reload_signal` is called
|
146
|
+
4. Reloads the page when heartbeat from new server is received
|
147
|
+
|
148
|
+
## Configuration
|
149
|
+
|
150
|
+
The package works out-of-the-box with default settings. No additional configuration is required.
|
151
|
+
|
152
|
+
## Limitations
|
153
|
+
|
154
|
+
- Unlike `uvicorn-hmr`, which does on-demand fine-grained reloading on the server side, this package simply reloads all the pages in the browser.
|
155
|
+
- Designed for development use only (not for production)
|
156
|
+
- Requires JavaScript to be enabled in the browser
|
157
|
+
|
158
|
+
## Contributing
|
159
|
+
|
160
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
@@ -0,0 +1,8 @@
|
|
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,,
|
@@ -1,84 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: fastapi-reloader
|
3
|
-
Version: 1.3.1
|
4
|
-
Project-URL: Homepage, https://github.com/promplate/hmr
|
5
|
-
Requires-Dist: asgi-lifespan~=2.0
|
6
|
-
Requires-Dist: fastapi~=0.115
|
7
|
-
Description-Content-Type: text/markdown
|
8
|
-
|
9
|
-
# FastAPI Reloader
|
10
|
-
|
11
|
-
[](https://pypi.org/project/fastapi-reloader/)
|
12
|
-
[](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.
|
@@ -1,8 +0,0 @@
|
|
1
|
-
fastapi_reloader-1.3.1.dist-info/METADATA,sha256=xKLO2Z0ZWciUVgTReqnzDklb611n6zA5PEthLU59ODA,2270
|
2
|
-
fastapi_reloader-1.3.1.dist-info/WHEEL,sha256=pz1FfwQ2kf9tI4G8U2ObRTKdvsTSmrreuBTtdnO8pJw,94
|
3
|
-
fastapi_reloader-1.3.1.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
4
|
-
fastapi_reloader/__init__.py,sha256=VE5m-0M92Weme8COqLs0LwIx5g6FHhqyOLGAgY6Zkzs,145
|
5
|
-
fastapi_reloader/core.py,sha256=eAyPhBZaFiKepy5NcU966adAJwqFTpHlK_uXn1cJQU8,1676
|
6
|
-
fastapi_reloader/patcher.py,sha256=nwn9sJW8KsXUr7dSPClWuPCanP453ymazfetVtuPaDA,2436
|
7
|
-
fastapi_reloader/runtime.js,sha256=pUPeMnMuKGHhBTudp6bFuNTTyZhGSvrvvWy_lWVN8nA,713
|
8
|
-
fastapi_reloader-1.3.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|