π p3: native_async decorator + sse_native + async markers (ADR-089)
swadhinbiswas committed 2 months ago
364904a
1 parent b750899
commit 364904a
1 file changed
+48 -0
Showing 1 changed file+48-0
βΌcrates/justapi-py/python/justapi/app.py
+48-0
| @@ -398,6 +398,36 @@ def adaptive_batch(max_size: int = 32, window_ms: int = 10): | ||
398 | 398 | return func |
399 | 399 | return wrapper |
400 | 400 | |
401 | + | |
402 | +def native_async(func=None): | |
403 | + """Mark a handler as **native-async** β its framework operations (DB | |
404 | + queries, SSE streaming, HTTP calls, serialization) run in Rust at native | |
405 | + speed, and the async dispatch uses the callback-driven resolution path | |
406 | + (no blocking wait, no thread hop). The user's own Python logic still runs | |
407 | + at Python speed (that is physics); everything the framework does for the | |
408 | + request is Rust (ADR-089). | |
409 | + | |
410 | + from justapi import JustAPIApp, native_async | |
411 | + | |
412 | + app = JustAPIApp() | |
413 | + | |
414 | + @app.get("/users/{id}") | |
415 | + @native_async | |
416 | + async def get_user(request): | |
417 | + row = await app.db.fetch_one("SELECT * FROM users WHERE id = ?", | |
418 | + [request.path_params["id"]]) | |
419 | + return row | |
420 | + | |
421 | + Use it on any async route whose heavy lifting is DB / IO / streaming. | |
422 | + """ | |
423 | + def wrapper(f): | |
424 | + f.__native_async__ = True | |
425 | + return f | |
426 | + | |
427 | + if func is None: | |
428 | + return wrapper | |
429 | + return wrapper(func) | |
430 | + | |
401 | 431 | class _AppState: |
402 | 432 | def __setattr__(self, name, value): |
403 | 433 | self.__dict__[name] = value |
| @@ -1034,6 +1064,12 @@ class JustAPIApp: | ||
1034 | 1064 | wrapper = sync_wrapper |
1035 | 1065 | |
1036 | 1066 | wrapper._needs_request = needs_request |
1067 | + # Per-handler flag: async handler (or async deps). Rust routes async | |
1068 | + # handlers to the parallel async pool (Granian model, ADR-087). | |
1069 | + wrapper._is_async = is_async | |
1070 | + # native_async marker: propagates `__native_async__` to the wrapper so | |
1071 | + # Rust can route it to the fastest dispatch (ADR-089). | |
1072 | + wrapper._is_native_async = bool(getattr(handler, "__native_async__", False)) | |
1037 | 1073 | return wrapper |
1038 | 1074 | |
1039 | 1075 | def _resolve_batch_config(self, func): |
| @@ -1899,6 +1935,18 @@ class JustAPIApp: | ||
1899 | 1935 | self._app.get(path, self._wrap_sse_handler(handler)) |
1900 | 1936 | return handler |
1901 | 1937 | |
1938 | + def sse_native(self, path: str, count: int = 10, interval_ms: int = 100): | |
1939 | + """Register a **Rust-native** Server-Sent Events route (ADR-088). | |
1940 | + | |
1941 | + The server streams ``count`` events at ``interval_ms`` spacing, generated | |
1942 | + entirely in Rust (tokio + mpsc + streaming response). No Python handler, | |
1943 | + no GIL, no generator pump β the stream runs at native speed. | |
1944 | + | |
1945 | + app.sse_native("/events", count=1000, interval_ms=0) # 1000 events, no delay | |
1946 | + """ | |
1947 | + self._app.sse_native(path, count, interval_ms) | |
1948 | + return self | |
1949 | + | |
1902 | 1950 | def websocket(self, path: str, handler=None): |
1903 | 1951 | """Register a WebSocket route. |
1904 | 1952 | |
1905 | 1953 | |