stario 0.1.0__tar.gz

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.
stario-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.3
2
+ Name: stario
3
+ Version: 0.1.0
4
+ Summary: Stario brings back the joy of building web apps
5
+ Author: Adam Bobowski
6
+ Author-email: Adam Bobowski <adam.bobowski@wratilabs.com>
7
+ Requires-Dist: brotli-asgi>=1.4.0
8
+ Requires-Dist: pydantic>=2.11.7
9
+ Requires-Dist: starlette>=0.47.2
10
+ Requires-Dist: uvicorn>=0.35.0
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+
14
+ <p align="center">
15
+ <picture>
16
+ <img alt="stario-logo" src="https://raw.githubusercontent.com/bobowski/stario/main/docs/img/stario.png">
17
+ </picture>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <em>✨ Binging back joy and exploration to building web apps ✨</em>
22
+ </p>
23
+
24
+ ---
25
+
26
+ **Documentation**: Coming soon
27
+
28
+ **Source Code**: <a href="https://github.com/bobowski/stario/tree/main/stario" target="_blank">https://github.com/bobowski/stario</a>
29
+
30
+ ---
31
+
32
+ ### Stario
33
+
34
+ Stario is a lightweight ASGI framework built on top of [Starlette](https://www.starlette.io/)
35
+ that brings back the joy of building web apps.
36
+ Focus on building apps rather than passing data between components.
37
+
38
+ ### Why Stario?
39
+
40
+ - HTML-first - dropping intermediary data layers (e.g. JSON) and using HTML as the primary data format. "Just throw html at user and make them happy."
41
+ - Rapid prototyping - a simple framework to build and test fullstack ideas with minimal setup. Build, see if it works, optimize later (if ever).
42
+ - No-nonsense - no ceremony, no complexity, just build.
43
+ - Learning experience - Learn about web, rather than X or Y language framework. I've learned a lot building this framework. I hope you will too.
44
+
45
+ It focuses on:
46
+
47
+ - **HTML-first DX**: return strings or HTML providers, no ceremony.
48
+ - **Realtime by default**: seamless Server-Sent Events (SSE) with [DataStar](https://data-star.dev/) patches/signals.
49
+ - **Great defaults**: Brotli compression out of the box, fast dependency resolution, and caching.
50
+ - **Starlette compatibility**: interop with routes, middleware, and tools you already know.
51
+
52
+ What you get:
53
+
54
+ - **A tiny, low-complexity HTTP framework** powered by [Starlette](https://www.starlette.io/) and [DataStar](https://data-star.dev/).
55
+ - **SSE streaming** by simply yielding patches, HTML, or signals (dicts).
56
+ - **Brotli compression** middleware enabled by default (GZip fallback).
57
+ - **Simple DI** via `Annotated` with request params (`QueryParam`, `PathParam`, `Header`, `Cookie`) and `Inject`.
58
+ - **Per-request/app/ttl caching** and configurable run modes (`auto`, `sync`, `thread`).
59
+ - **Header-constrained routes** and a lightweight `StarRouter`.
60
+ - **Few dependencies** and a clear, typed codebase.
61
+
62
+ ---
63
+
64
+ ## Installation
65
+
66
+ From PyPI (when available):
67
+
68
+ ```shell
69
+ pip install stario
70
+ ```
71
+
72
+ Or from source in this repository:
73
+
74
+ ```shell
75
+ cd stario
76
+ pip install -e .
77
+ ```
78
+
79
+ You'll also want an ASGI server such as [uvicorn](https://www.uvicorn.org/):
80
+
81
+ ```shell
82
+ pip install uvicorn
83
+ ```
84
+
85
+ ## Quick start
86
+
87
+ ```python
88
+ # main.py
89
+ import asyncio
90
+ from stario import Stario, Query, Command
91
+ from stario.toys import ToyPage
92
+
93
+
94
+ async def home():
95
+ return ToyPage(
96
+ """
97
+ <h2>Realtime responses!</h2>
98
+ <div data-on-load="@get('/online-counter')">
99
+ This shows how long the connection has been open.
100
+ </div>
101
+ <div id="online-counter"></div>
102
+ """
103
+ )
104
+
105
+
106
+ async def online_counter():
107
+ duration = 0
108
+ interval = 0.01
109
+ while True:
110
+ # Yielding strings/patches streams as SSE with DataStar-compatible events
111
+ yield f"<div id='online-counter'>Online since: {duration:.1f}s</div>"
112
+ duration += interval
113
+ await asyncio.sleep(interval)
114
+
115
+
116
+ # Building the app
117
+ app = Stario(
118
+ Query("/", home),
119
+ Query("/online-counter", online_counter),
120
+ )
121
+ ```
122
+
123
+ Run with Uvicorn:
124
+
125
+ ```shell
126
+ uvicorn main:app --reload
127
+ ```
128
+
129
+ Open `http://127.0.0.1:8000/` in your browser.
130
+
131
+ ---
132
+
133
+ <p align="center"><i>Stario code is designed & crafted for joy.</i><br/>&mdash; ⭐️ &mdash;</p>
stario-0.1.0/README.md ADDED
@@ -0,0 +1,120 @@
1
+ <p align="center">
2
+ <picture>
3
+ <img alt="stario-logo" src="https://raw.githubusercontent.com/bobowski/stario/main/docs/img/stario.png">
4
+ </picture>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <em>✨ Binging back joy and exploration to building web apps ✨</em>
9
+ </p>
10
+
11
+ ---
12
+
13
+ **Documentation**: Coming soon
14
+
15
+ **Source Code**: <a href="https://github.com/bobowski/stario/tree/main/stario" target="_blank">https://github.com/bobowski/stario</a>
16
+
17
+ ---
18
+
19
+ ### Stario
20
+
21
+ Stario is a lightweight ASGI framework built on top of [Starlette](https://www.starlette.io/)
22
+ that brings back the joy of building web apps.
23
+ Focus on building apps rather than passing data between components.
24
+
25
+ ### Why Stario?
26
+
27
+ - HTML-first - dropping intermediary data layers (e.g. JSON) and using HTML as the primary data format. "Just throw html at user and make them happy."
28
+ - Rapid prototyping - a simple framework to build and test fullstack ideas with minimal setup. Build, see if it works, optimize later (if ever).
29
+ - No-nonsense - no ceremony, no complexity, just build.
30
+ - Learning experience - Learn about web, rather than X or Y language framework. I've learned a lot building this framework. I hope you will too.
31
+
32
+ It focuses on:
33
+
34
+ - **HTML-first DX**: return strings or HTML providers, no ceremony.
35
+ - **Realtime by default**: seamless Server-Sent Events (SSE) with [DataStar](https://data-star.dev/) patches/signals.
36
+ - **Great defaults**: Brotli compression out of the box, fast dependency resolution, and caching.
37
+ - **Starlette compatibility**: interop with routes, middleware, and tools you already know.
38
+
39
+ What you get:
40
+
41
+ - **A tiny, low-complexity HTTP framework** powered by [Starlette](https://www.starlette.io/) and [DataStar](https://data-star.dev/).
42
+ - **SSE streaming** by simply yielding patches, HTML, or signals (dicts).
43
+ - **Brotli compression** middleware enabled by default (GZip fallback).
44
+ - **Simple DI** via `Annotated` with request params (`QueryParam`, `PathParam`, `Header`, `Cookie`) and `Inject`.
45
+ - **Per-request/app/ttl caching** and configurable run modes (`auto`, `sync`, `thread`).
46
+ - **Header-constrained routes** and a lightweight `StarRouter`.
47
+ - **Few dependencies** and a clear, typed codebase.
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ From PyPI (when available):
54
+
55
+ ```shell
56
+ pip install stario
57
+ ```
58
+
59
+ Or from source in this repository:
60
+
61
+ ```shell
62
+ cd stario
63
+ pip install -e .
64
+ ```
65
+
66
+ You'll also want an ASGI server such as [uvicorn](https://www.uvicorn.org/):
67
+
68
+ ```shell
69
+ pip install uvicorn
70
+ ```
71
+
72
+ ## Quick start
73
+
74
+ ```python
75
+ # main.py
76
+ import asyncio
77
+ from stario import Stario, Query, Command
78
+ from stario.toys import ToyPage
79
+
80
+
81
+ async def home():
82
+ return ToyPage(
83
+ """
84
+ <h2>Realtime responses!</h2>
85
+ <div data-on-load="@get('/online-counter')">
86
+ This shows how long the connection has been open.
87
+ </div>
88
+ <div id="online-counter"></div>
89
+ """
90
+ )
91
+
92
+
93
+ async def online_counter():
94
+ duration = 0
95
+ interval = 0.01
96
+ while True:
97
+ # Yielding strings/patches streams as SSE with DataStar-compatible events
98
+ yield f"<div id='online-counter'>Online since: {duration:.1f}s</div>"
99
+ duration += interval
100
+ await asyncio.sleep(interval)
101
+
102
+
103
+ # Building the app
104
+ app = Stario(
105
+ Query("/", home),
106
+ Query("/online-counter", online_counter),
107
+ )
108
+ ```
109
+
110
+ Run with Uvicorn:
111
+
112
+ ```shell
113
+ uvicorn main:app --reload
114
+ ```
115
+
116
+ Open `http://127.0.0.1:8000/` in your browser.
117
+
118
+ ---
119
+
120
+ <p align="center"><i>Stario code is designed & crafted for joy.</i><br/>&mdash; ⭐️ &mdash;</p>
@@ -0,0 +1,27 @@
1
+ [project]
2
+ name = "stario"
3
+ version = "0.1.0"
4
+ description = "Stario brings back the joy of building web apps"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Adam Bobowski", email = "adam.bobowski@wratilabs.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "brotli-asgi>=1.4.0",
12
+ "pydantic>=2.11.7",
13
+ "starlette>=0.47.2",
14
+ "uvicorn>=0.35.0",
15
+ ]
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.8.6,<0.9.0"]
19
+ build-backend = "uv_build"
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "pyright>=1.1.403",
24
+ "pytest>=8.4.1",
25
+ "ruff>=0.12.8",
26
+ "ty>=0.0.1a19",
27
+ ]
@@ -0,0 +1,26 @@
1
+ from .application import Stario
2
+ from .datastar import LOAD_DATASTAR, Signals
3
+ from .dependencies import Inject
4
+ from .middlewares import BrotliMiddleware
5
+ from .parameters import Body, Cookie, Header, PathParam, QueryParam
6
+ from .routes import Command, Query
7
+ from .routing import StarRoute
8
+ from .types import CacheScope, RunMode
9
+
10
+ __all__ = [
11
+ "Stario",
12
+ "StarRoute",
13
+ "Query",
14
+ "Command",
15
+ "PathParam",
16
+ "QueryParam",
17
+ "Body",
18
+ "Header",
19
+ "Cookie",
20
+ "Inject",
21
+ "Signals",
22
+ "LOAD_DATASTAR",
23
+ "BrotliMiddleware",
24
+ "CacheScope",
25
+ "RunMode",
26
+ ]
@@ -0,0 +1,150 @@
1
+ from typing import (
2
+ Any,
3
+ Callable,
4
+ Mapping,
5
+ ParamSpec,
6
+ Protocol,
7
+ Sequence,
8
+ TypeVar,
9
+ )
10
+
11
+ from starlette.applications import Starlette
12
+ from starlette.datastructures import State, URLPath
13
+ from starlette.middleware import Middleware
14
+ from starlette.routing import BaseRoute
15
+ from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send
16
+
17
+ from stario.middlewares import BrotliMiddleware
18
+ from stario.routing import StarRouter
19
+
20
+ AppType = TypeVar("AppType", bound="Stario")
21
+
22
+ P = ParamSpec("P")
23
+
24
+
25
+ class _MiddlewareFactory(Protocol[P]):
26
+ def __call__(
27
+ self, app: ASGIApp, /, *args: P.args, **kwargs: P.kwargs
28
+ ) -> ASGIApp: ...
29
+
30
+
31
+ class Stario:
32
+ """
33
+ Creates a Stario application.
34
+ It's 'almost' Starlette app, but we push on some of the details.
35
+ """
36
+
37
+ def __init__(
38
+ self: AppType,
39
+ *routes: BaseRoute,
40
+ middleware: Sequence[Middleware] | None = None,
41
+ compression_middleware: Middleware | None = BrotliMiddleware.as_middleware(),
42
+ exception_handlers: Mapping[Any, ExceptionHandler] | None = None,
43
+ lifespan: Lifespan[AppType] | None = None,
44
+ debug: bool = False,
45
+ router_class: type[StarRouter] = StarRouter,
46
+ ) -> None:
47
+ """Initializes the application.
48
+
49
+ Parameters:
50
+ routes: A list of routes to serve incoming HTTP and WebSocket requests.
51
+ middleware: A list of middleware to run for every request. A starlette
52
+ application will always automatically include two middleware classes.
53
+ `ServerErrorMiddleware` is added as the very outermost middleware, to handle
54
+ any uncaught errors occurring anywhere in the entire stack.
55
+ `ExceptionMiddleware` is added as the very innermost middleware, to deal
56
+ with handled exception cases occurring in the routing or endpoints.
57
+ compression_middleware: A middleware class to compress the responses.
58
+ By default we opt for brotli compression with gzip fallback.
59
+ Parameters are what we think are reasonable for most use cases.
60
+ If you need tweaking those try `BrotliMiddleware.as_middleware()`.
61
+ exception_handlers: A mapping of either integer status codes,
62
+ or exception class types onto callables which handle the exceptions.
63
+ Exception handler callables should be of the form
64
+ `handler(request, exc) -> response` and may be either standard functions, or
65
+ async functions.
66
+ lifespan: A lifespan context function, which can be used to perform
67
+ startup and shutdown tasks. This is a newer style that replaces the
68
+ `on_startup` and `on_shutdown` handlers. Use one or the other, not both.
69
+ debug: Boolean indicating if debug tracebacks should be returned on errors.
70
+ router_class: A class to use for the router. By default we use `StarRouter`.
71
+ You can use this to customize the behaviour of the app, just consider
72
+ what are the implications :)
73
+ """
74
+
75
+ self.debug = debug
76
+ self.state = State()
77
+ self.router = router_class(*routes, lifespan=lifespan)
78
+ self.exception_handlers = (
79
+ {} if exception_handlers is None else dict(exception_handlers)
80
+ )
81
+ if compression_middleware is not None:
82
+ middleware = [] if middleware is None else list(middleware)
83
+ middleware.insert(0, compression_middleware)
84
+ self.user_middleware = [] if middleware is None else list(middleware)
85
+ self.middleware_stack: ASGIApp | None = None
86
+
87
+ self.cache: dict[Callable, Any] = {}
88
+
89
+ # self.dependency_overrides: dict[Callable, Callable] = {}
90
+ # """
91
+ # A dictionary with overrides for the dependencies.
92
+
93
+ # Each key is the original dependency callable, and the value is the
94
+ # actual dependency that should be called.
95
+
96
+ # This is for testing, to replace expensive dependencies with testing
97
+ # versions.
98
+ # """
99
+
100
+ @property
101
+ def routes(self) -> list[BaseRoute]:
102
+ return self.router.routes
103
+
104
+ def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:
105
+ return self.router.url_path_for(name, **path_params)
106
+
107
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
108
+ scope["app"] = self
109
+ if self.middleware_stack is None:
110
+ # Delegate to Starlette to build the middleware stack
111
+ # - we like it so there's no need to re-implement it
112
+ # Starlette expects its own instance; use the instance method to
113
+ # construct the stack with our attributes
114
+ self.middleware_stack = Starlette.build_middleware_stack(self) # type: ignore[arg-type]
115
+ await self.middleware_stack(scope, receive, send)
116
+
117
+ def add(self, route: BaseRoute) -> None:
118
+ # We diverge from Starlette here because I think having more control over
119
+ # the process of adding routes is more important for us in context of this library
120
+ self.router.add(route)
121
+
122
+ def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
123
+ self.router.mount(path, app=app, name=name)
124
+
125
+ def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
126
+ self.router.host(host, app=app, name=name)
127
+
128
+ def add_middleware(
129
+ self,
130
+ middleware_class: _MiddlewareFactory[P],
131
+ *args: P.args,
132
+ **kwargs: P.kwargs,
133
+ ) -> None:
134
+ if self.middleware_stack is not None:
135
+ raise RuntimeError("Cannot add middleware after an application has started")
136
+ self.user_middleware.insert(0, Middleware(middleware_class, *args, **kwargs))
137
+
138
+ def add_exception_handler(
139
+ self,
140
+ exc_class_or_status_code: int | type[Exception],
141
+ handler: ExceptionHandler,
142
+ ) -> None:
143
+ self.exception_handlers[exc_class_or_status_code] = handler
144
+
145
+ def add_event_handler(
146
+ self,
147
+ event_type: str,
148
+ func: Callable,
149
+ ) -> None:
150
+ self.router.add_event_handler(event_type, func)