slick-framework 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nehz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: slick-framework
3
+ Version: 0.1.0
4
+ Summary: A slick, minimal ASGI web framework built on the Python standard library.
5
+ Author: nehz
6
+ License: MIT
7
+ Keywords: asgi,web,framework,http,api,microframework
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Framework :: AsyncIO
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
23
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: server
29
+ Requires-Dist: uvicorn>=0.23; extra == "server"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # slick-framework
35
+
36
+ **A slick, minimal ASGI web framework for Python, built only on the standard library.**
37
+
38
+ `slick` covers what most small services need: routing with typed path parameters,
39
+ request and response objects, JSON in and out, middleware, lifespan hooks, an
40
+ in-process test client and a development server. It has zero runtime dependencies,
41
+ and the core is a few hundred lines you can read in one sitting.
42
+
43
+ ```python
44
+ from slick import Slick
45
+
46
+ app = Slick()
47
+
48
+ @app.get("/hello/{name}")
49
+ async def hello(request, name: str):
50
+ return {"message": f"Hello, {name}!"}
51
+ ```
52
+
53
+ ## Features
54
+
55
+ - **ASGI 3 app**: runs under uvicorn, hypercorn or any other ASGI server.
56
+ - **Routing** with `{name}`, `{id:int}`, `{x:float}` and `{rest:path}` parameters,
57
+ converted values, automatic `HEAD` for `GET` routes, and proper `404`/`405` (with `Allow`) responses.
58
+ - **Path params injected as keyword arguments** when the handler's signature names them.
59
+ - **Return plain values**: `dict`/`list` becomes JSON, `str` becomes text, `None` becomes `204`,
60
+ and `(body, status[, headers])` tuples set the status and headers.
61
+ - **Request object** with headers, query params, cookies, `await request.json()` / `.form()` / `.body()`.
62
+ - **Responses**: `Response`, `JSONResponse`, `PlainTextResponse`, `HTMLResponse`, `RedirectResponse`, plus cookies.
63
+ - **Middleware**: plain `async def mw(request, call_next)` functions.
64
+ - **Errors**: `HTTPException`, plus custom handlers for any exception type.
65
+ - **Lifespan hooks**: `@app.on_startup` / `@app.on_shutdown`, and `app.state` / `request.state`.
66
+ - **Sub-routers**: `Router()` and `app.include_router(router, prefix="/api")`.
67
+ - **`TestClient`**: synchronous and in-process, with no sockets and nothing extra to install.
68
+ - **`serve()`**: a stdlib-only development server, so examples run straight away.
69
+ - Sync handlers are supported too (they run in a worker thread). Fully type-hinted (`py.typed`).
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ pip install slick-framework # core, no dependencies
75
+ pip install "slick-framework[server]" # optional: adds uvicorn for production serving
76
+ ```
77
+
78
+ Requires Python 3.9+.
79
+
80
+ ## Quickstart
81
+
82
+ ```python
83
+ # app.py
84
+ from slick import HTTPException, Request, Slick, serve
85
+
86
+ app = Slick()
87
+ BOOKS = {1: {"id": 1, "title": "Dune"}}
88
+
89
+ @app.get("/books/{book_id:int}")
90
+ async def get_book(request: Request, book_id: int):
91
+ if book_id not in BOOKS:
92
+ raise HTTPException(404, "No such book")
93
+ return BOOKS[book_id]
94
+
95
+ @app.post("/books")
96
+ async def add_book(request: Request):
97
+ data = await request.json() # malformed JSON -> 400 automatically
98
+ book = {"id": len(BOOKS) + 1, "title": data["title"]}
99
+ BOOKS[book["id"]] = book
100
+ return book, 201
101
+
102
+ if __name__ == "__main__":
103
+ serve(app, port=8000) # or: uvicorn app:app
104
+ ```
105
+
106
+ Test it without a network:
107
+
108
+ ```python
109
+ from slick import TestClient
110
+ from app import app
111
+
112
+ with TestClient(app) as client: # `with` also runs startup/shutdown hooks
113
+ assert client.get("/books/1").json() == {"id": 1, "title": "Dune"}
114
+ assert client.post("/books", json={"title": "Emma"}).status_code == 201
115
+ assert client.get("/books/99").status_code == 404
116
+ ```
117
+
118
+ A fuller example (a todo CRUD API with a sub-router, middleware and startup hook) is in
119
+ [`examples/todo_app.py`](examples/todo_app.py):
120
+
121
+ ```bash
122
+ python3 examples/todo_app.py # after `pip install -e .`; or prefix with PYTHONPATH=src
123
+ ```
124
+
125
+ ## API overview
126
+
127
+ Everything below is importable from `slick`.
128
+
129
+ ### `Slick(*, debug=False)`
130
+
131
+ The application, and an ASGI callable. It subclasses `Router`, so it has all of the router methods.
132
+
133
+ | Member | Description |
134
+ | --- | --- |
135
+ | `@app.get(path, name=None)`, `.post`, `.put`, `.patch`, `.delete` | Register a handler for one method. `GET` also answers `HEAD`. |
136
+ | `@app.route(path, methods=None, name=None)` | Register for several methods (default `["GET"]`). |
137
+ | `app.add_route(path, handler, methods=None, name=None) -> Route` | Non-decorator form. |
138
+ | `app.include_router(router, prefix="")` | Copy a `Router`'s routes in under `prefix`. |
139
+ | `app.url_for(name, **params) -> str` | Build a path from a route name. The name defaults to the handler's `__name__`. |
140
+ | `@app.middleware` | Register `async def mw(request, call_next) -> Response`. The first registered is outermost. |
141
+ | `@app.exception_handler(ExcClass)` | Register `handler(request, exc)`, sync or async, for that class and its subclasses. |
142
+ | `@app.on_startup`, `@app.on_shutdown` | Lifespan hooks taking no arguments, sync or async. |
143
+ | `app.state` | A `State` attribute bag for app-wide objects. |
144
+ | `app.debug` | If true, an unhandled error returns its traceback as a `500` text body. |
145
+
146
+ **Handlers** are `handler(request, **path_params)`, `async def` or plain `def`. A path
147
+ param is passed as a keyword argument only if the signature names it or accepts
148
+ `**kwargs`. Otherwise read it from `request.path_params`. A handler may return:
149
+
150
+ | Return value | Response |
151
+ | --- | --- |
152
+ | `Response` (or subclass) | sent as-is |
153
+ | `dict` / `list` | `JSONResponse` |
154
+ | `str` | `PlainTextResponse` |
155
+ | `bytes` | `Response` with `application/octet-stream` |
156
+ | `None` | empty `204 No Content` |
157
+ | `(value, status)` / `(value, status, headers)` | the above, with that status and extra headers |
158
+
159
+ Middleware may return the same kinds of value.
160
+
161
+ **Path converters**: `str` (default, one segment), `int` (optionally negative), `float`, `path` (can include `/`).
162
+ Matching is exact, with no trailing-slash redirects. Routes are tried in registration order.
163
+
164
+ **Errors**: `HTTPException` and unmatched routes render as `{"detail": ...}` JSON, and
165
+ middleware sees them as normal responses. Any other uncaught exception becomes a
166
+ `500 {"detail": "Internal Server Error"}`, is logged on the `slick` logger and is re-raised to the server.
167
+
168
+ ### `Router()`
169
+
170
+ A collection of routes that has the same `get/post/put/patch/delete/route/add_route/include_router/url_for`
171
+ methods as the app, plus `resolve(method, path) -> (Route, params)`. `Route(path, handler, methods=None, name=None)`
172
+ provides `.match(path)` and `.url_for(**params)`.
173
+
174
+ ### `Request`
175
+
176
+ | Member | Description |
177
+ | --- | --- |
178
+ | `method`, `path`, `url` | Upper-case method, decoded path, and the reconstructed absolute URL. |
179
+ | `headers` | Case-insensitive `Headers`. |
180
+ | `query_params` | `QueryParams`: `get(key, default)` returns the first value, `getlist(key)` returns all values. |
181
+ | `path_params` | `dict` of converted route parameters. |
182
+ | `cookies` | `dict` parsed from the `Cookie` header. |
183
+ | `client` | `(host, port)` or `None`. |
184
+ | `state` | A per-request `State`, for example set by middleware. |
185
+ | `app`, `scope` | The `Slick` app and the raw ASGI scope. |
186
+ | `await body()`, `await text()` | The raw body (cached) and the body decoded as UTF-8. |
187
+ | `await json()` | The parsed body. Raises `HTTPException(400)` on invalid JSON. |
188
+ | `await form()` | A `dict` from a urlencoded body. |
189
+
190
+ ### Responses
191
+
192
+ - `Response(content=None, status_code=200, headers=None, media_type=None)`: `content` is `bytes`, `str` or `None`.
193
+ It has `.status_code`, `.body`, a mutable `.headers` (`Headers`) and `.set_cookie(key, value="", *, max_age=None, path="/", httponly=False, secure=False, samesite="lax")`.
194
+ - `JSONResponse(content, status_code=200, headers=None, *, indent=None, default=None)` produces compact UTF-8 JSON by default.
195
+ - `PlainTextResponse`, `HTMLResponse` take the same arguments as `Response`.
196
+ - `RedirectResponse(url, status_code=307, headers=None)`.
197
+ - `to_response(value)` applies the return-value rules above.
198
+
199
+ ### `HTTPException(status_code, detail=None, headers=None)`
200
+
201
+ When `detail` is omitted it defaults to the standard reason phrase (for example `"Not Found"`).
202
+
203
+ ### `TestClient(app, base_url="http://testserver", raise_server_exceptions=True)`
204
+
205
+ - `.get/.head/.post/.put/.patch/.delete/.options(url, *, params=None, headers=None, json=None, data=None, cookies=None)`
206
+ and `.request(method, url, ...)` return a `TestResponse`. `data` takes a form mapping, `str` or `bytes`.
207
+ - `with TestClient(app) as client:` runs the app's startup and shutdown hooks. `.close()` releases the event loop.
208
+ - `Set-Cookie` values are stored in `client.cookies` and sent on later requests.
209
+ - With `raise_server_exceptions=False` you get the `500` response instead of the exception.
210
+ - `TestResponse` has `.status_code`, `.headers`, `.content`, `.text`, `.json()` and `.ok`.
211
+
212
+ ### `serve(app, host="127.0.0.1", port=8000)`
213
+
214
+ A threaded development server built on `http.server`. It runs lifespan hooks and stops on Ctrl+C.
215
+ It buffers whole bodies and is not meant for production; use `uvicorn yourmodule:app` there.
216
+
217
+ ### Data structures
218
+
219
+ `Headers` (case-insensitive and multi-valued: `getlist`, `append`, `multi_items`, `raw`, `from_raw`),
220
+ `QueryParams` (`getlist`, `multi_items`), and `State` (an attribute bag with `.get(name, default)`).
221
+
222
+ ## Development
223
+
224
+ ```bash
225
+ python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
226
+ .venv/bin/python -m pytest # or, with no installs at all:
227
+ PYTHONPATH=src python3 -m unittest discover -s tests
228
+ ```
229
+
230
+ ## License
231
+
232
+ MIT
@@ -0,0 +1,199 @@
1
+ # slick-framework
2
+
3
+ **A slick, minimal ASGI web framework for Python, built only on the standard library.**
4
+
5
+ `slick` covers what most small services need: routing with typed path parameters,
6
+ request and response objects, JSON in and out, middleware, lifespan hooks, an
7
+ in-process test client and a development server. It has zero runtime dependencies,
8
+ and the core is a few hundred lines you can read in one sitting.
9
+
10
+ ```python
11
+ from slick import Slick
12
+
13
+ app = Slick()
14
+
15
+ @app.get("/hello/{name}")
16
+ async def hello(request, name: str):
17
+ return {"message": f"Hello, {name}!"}
18
+ ```
19
+
20
+ ## Features
21
+
22
+ - **ASGI 3 app**: runs under uvicorn, hypercorn or any other ASGI server.
23
+ - **Routing** with `{name}`, `{id:int}`, `{x:float}` and `{rest:path}` parameters,
24
+ converted values, automatic `HEAD` for `GET` routes, and proper `404`/`405` (with `Allow`) responses.
25
+ - **Path params injected as keyword arguments** when the handler's signature names them.
26
+ - **Return plain values**: `dict`/`list` becomes JSON, `str` becomes text, `None` becomes `204`,
27
+ and `(body, status[, headers])` tuples set the status and headers.
28
+ - **Request object** with headers, query params, cookies, `await request.json()` / `.form()` / `.body()`.
29
+ - **Responses**: `Response`, `JSONResponse`, `PlainTextResponse`, `HTMLResponse`, `RedirectResponse`, plus cookies.
30
+ - **Middleware**: plain `async def mw(request, call_next)` functions.
31
+ - **Errors**: `HTTPException`, plus custom handlers for any exception type.
32
+ - **Lifespan hooks**: `@app.on_startup` / `@app.on_shutdown`, and `app.state` / `request.state`.
33
+ - **Sub-routers**: `Router()` and `app.include_router(router, prefix="/api")`.
34
+ - **`TestClient`**: synchronous and in-process, with no sockets and nothing extra to install.
35
+ - **`serve()`**: a stdlib-only development server, so examples run straight away.
36
+ - Sync handlers are supported too (they run in a worker thread). Fully type-hinted (`py.typed`).
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install slick-framework # core, no dependencies
42
+ pip install "slick-framework[server]" # optional: adds uvicorn for production serving
43
+ ```
44
+
45
+ Requires Python 3.9+.
46
+
47
+ ## Quickstart
48
+
49
+ ```python
50
+ # app.py
51
+ from slick import HTTPException, Request, Slick, serve
52
+
53
+ app = Slick()
54
+ BOOKS = {1: {"id": 1, "title": "Dune"}}
55
+
56
+ @app.get("/books/{book_id:int}")
57
+ async def get_book(request: Request, book_id: int):
58
+ if book_id not in BOOKS:
59
+ raise HTTPException(404, "No such book")
60
+ return BOOKS[book_id]
61
+
62
+ @app.post("/books")
63
+ async def add_book(request: Request):
64
+ data = await request.json() # malformed JSON -> 400 automatically
65
+ book = {"id": len(BOOKS) + 1, "title": data["title"]}
66
+ BOOKS[book["id"]] = book
67
+ return book, 201
68
+
69
+ if __name__ == "__main__":
70
+ serve(app, port=8000) # or: uvicorn app:app
71
+ ```
72
+
73
+ Test it without a network:
74
+
75
+ ```python
76
+ from slick import TestClient
77
+ from app import app
78
+
79
+ with TestClient(app) as client: # `with` also runs startup/shutdown hooks
80
+ assert client.get("/books/1").json() == {"id": 1, "title": "Dune"}
81
+ assert client.post("/books", json={"title": "Emma"}).status_code == 201
82
+ assert client.get("/books/99").status_code == 404
83
+ ```
84
+
85
+ A fuller example (a todo CRUD API with a sub-router, middleware and startup hook) is in
86
+ [`examples/todo_app.py`](examples/todo_app.py):
87
+
88
+ ```bash
89
+ python3 examples/todo_app.py # after `pip install -e .`; or prefix with PYTHONPATH=src
90
+ ```
91
+
92
+ ## API overview
93
+
94
+ Everything below is importable from `slick`.
95
+
96
+ ### `Slick(*, debug=False)`
97
+
98
+ The application, and an ASGI callable. It subclasses `Router`, so it has all of the router methods.
99
+
100
+ | Member | Description |
101
+ | --- | --- |
102
+ | `@app.get(path, name=None)`, `.post`, `.put`, `.patch`, `.delete` | Register a handler for one method. `GET` also answers `HEAD`. |
103
+ | `@app.route(path, methods=None, name=None)` | Register for several methods (default `["GET"]`). |
104
+ | `app.add_route(path, handler, methods=None, name=None) -> Route` | Non-decorator form. |
105
+ | `app.include_router(router, prefix="")` | Copy a `Router`'s routes in under `prefix`. |
106
+ | `app.url_for(name, **params) -> str` | Build a path from a route name. The name defaults to the handler's `__name__`. |
107
+ | `@app.middleware` | Register `async def mw(request, call_next) -> Response`. The first registered is outermost. |
108
+ | `@app.exception_handler(ExcClass)` | Register `handler(request, exc)`, sync or async, for that class and its subclasses. |
109
+ | `@app.on_startup`, `@app.on_shutdown` | Lifespan hooks taking no arguments, sync or async. |
110
+ | `app.state` | A `State` attribute bag for app-wide objects. |
111
+ | `app.debug` | If true, an unhandled error returns its traceback as a `500` text body. |
112
+
113
+ **Handlers** are `handler(request, **path_params)`, `async def` or plain `def`. A path
114
+ param is passed as a keyword argument only if the signature names it or accepts
115
+ `**kwargs`. Otherwise read it from `request.path_params`. A handler may return:
116
+
117
+ | Return value | Response |
118
+ | --- | --- |
119
+ | `Response` (or subclass) | sent as-is |
120
+ | `dict` / `list` | `JSONResponse` |
121
+ | `str` | `PlainTextResponse` |
122
+ | `bytes` | `Response` with `application/octet-stream` |
123
+ | `None` | empty `204 No Content` |
124
+ | `(value, status)` / `(value, status, headers)` | the above, with that status and extra headers |
125
+
126
+ Middleware may return the same kinds of value.
127
+
128
+ **Path converters**: `str` (default, one segment), `int` (optionally negative), `float`, `path` (can include `/`).
129
+ Matching is exact, with no trailing-slash redirects. Routes are tried in registration order.
130
+
131
+ **Errors**: `HTTPException` and unmatched routes render as `{"detail": ...}` JSON, and
132
+ middleware sees them as normal responses. Any other uncaught exception becomes a
133
+ `500 {"detail": "Internal Server Error"}`, is logged on the `slick` logger and is re-raised to the server.
134
+
135
+ ### `Router()`
136
+
137
+ A collection of routes that has the same `get/post/put/patch/delete/route/add_route/include_router/url_for`
138
+ methods as the app, plus `resolve(method, path) -> (Route, params)`. `Route(path, handler, methods=None, name=None)`
139
+ provides `.match(path)` and `.url_for(**params)`.
140
+
141
+ ### `Request`
142
+
143
+ | Member | Description |
144
+ | --- | --- |
145
+ | `method`, `path`, `url` | Upper-case method, decoded path, and the reconstructed absolute URL. |
146
+ | `headers` | Case-insensitive `Headers`. |
147
+ | `query_params` | `QueryParams`: `get(key, default)` returns the first value, `getlist(key)` returns all values. |
148
+ | `path_params` | `dict` of converted route parameters. |
149
+ | `cookies` | `dict` parsed from the `Cookie` header. |
150
+ | `client` | `(host, port)` or `None`. |
151
+ | `state` | A per-request `State`, for example set by middleware. |
152
+ | `app`, `scope` | The `Slick` app and the raw ASGI scope. |
153
+ | `await body()`, `await text()` | The raw body (cached) and the body decoded as UTF-8. |
154
+ | `await json()` | The parsed body. Raises `HTTPException(400)` on invalid JSON. |
155
+ | `await form()` | A `dict` from a urlencoded body. |
156
+
157
+ ### Responses
158
+
159
+ - `Response(content=None, status_code=200, headers=None, media_type=None)`: `content` is `bytes`, `str` or `None`.
160
+ It has `.status_code`, `.body`, a mutable `.headers` (`Headers`) and `.set_cookie(key, value="", *, max_age=None, path="/", httponly=False, secure=False, samesite="lax")`.
161
+ - `JSONResponse(content, status_code=200, headers=None, *, indent=None, default=None)` produces compact UTF-8 JSON by default.
162
+ - `PlainTextResponse`, `HTMLResponse` take the same arguments as `Response`.
163
+ - `RedirectResponse(url, status_code=307, headers=None)`.
164
+ - `to_response(value)` applies the return-value rules above.
165
+
166
+ ### `HTTPException(status_code, detail=None, headers=None)`
167
+
168
+ When `detail` is omitted it defaults to the standard reason phrase (for example `"Not Found"`).
169
+
170
+ ### `TestClient(app, base_url="http://testserver", raise_server_exceptions=True)`
171
+
172
+ - `.get/.head/.post/.put/.patch/.delete/.options(url, *, params=None, headers=None, json=None, data=None, cookies=None)`
173
+ and `.request(method, url, ...)` return a `TestResponse`. `data` takes a form mapping, `str` or `bytes`.
174
+ - `with TestClient(app) as client:` runs the app's startup and shutdown hooks. `.close()` releases the event loop.
175
+ - `Set-Cookie` values are stored in `client.cookies` and sent on later requests.
176
+ - With `raise_server_exceptions=False` you get the `500` response instead of the exception.
177
+ - `TestResponse` has `.status_code`, `.headers`, `.content`, `.text`, `.json()` and `.ok`.
178
+
179
+ ### `serve(app, host="127.0.0.1", port=8000)`
180
+
181
+ A threaded development server built on `http.server`. It runs lifespan hooks and stops on Ctrl+C.
182
+ It buffers whole bodies and is not meant for production; use `uvicorn yourmodule:app` there.
183
+
184
+ ### Data structures
185
+
186
+ `Headers` (case-insensitive and multi-valued: `getlist`, `append`, `multi_items`, `raw`, `from_raw`),
187
+ `QueryParams` (`getlist`, `multi_items`), and `State` (an attribute bag with `.get(name, default)`).
188
+
189
+ ## Development
190
+
191
+ ```bash
192
+ python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
193
+ .venv/bin/python -m pytest # or, with no installs at all:
194
+ PYTHONPATH=src python3 -m unittest discover -s tests
195
+ ```
196
+
197
+ ## License
198
+
199
+ MIT
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "slick-framework"
7
+ version = "0.1.0"
8
+ description = "A slick, minimal ASGI web framework built on the Python standard library."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "nehz" }]
13
+ keywords = ["asgi", "web", "framework", "http", "api", "microframework"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Web Environment",
17
+ "Framework :: AsyncIO",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Internet :: WWW/HTTP",
29
+ "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
30
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
31
+ "Typing :: Typed",
32
+ ]
33
+ dependencies = []
34
+
35
+ [project.optional-dependencies]
36
+ # Only needed to run apps under a production-grade ASGI server.
37
+ server = ["uvicorn>=0.23"]
38
+ dev = ["pytest>=7"]
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.setuptools.package-data]
44
+ slick = ["py.typed"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,52 @@
1
+ """slick - a slick, minimal ASGI web framework built on the standard library.
2
+
3
+ Quick taste::
4
+
5
+ from slick import Slick
6
+
7
+ app = Slick()
8
+
9
+ @app.get("/hello/{name}")
10
+ async def hello(request, name: str):
11
+ return {"message": f"Hello, {name}!"}
12
+ """
13
+
14
+ from .app import CallNext, Slick
15
+ from .datastructures import Headers, QueryParams, State
16
+ from .exceptions import HTTPException
17
+ from .requests import Request
18
+ from .responses import (
19
+ HTMLResponse,
20
+ JSONResponse,
21
+ PlainTextResponse,
22
+ RedirectResponse,
23
+ Response,
24
+ to_response,
25
+ )
26
+ from .routing import Route, Router
27
+ from .server import serve
28
+ from .testclient import TestClient, TestResponse
29
+
30
+ __version__ = "0.1.0"
31
+
32
+ __all__ = [
33
+ "Slick",
34
+ "Router",
35
+ "Route",
36
+ "Request",
37
+ "Response",
38
+ "JSONResponse",
39
+ "PlainTextResponse",
40
+ "HTMLResponse",
41
+ "RedirectResponse",
42
+ "HTTPException",
43
+ "Headers",
44
+ "QueryParams",
45
+ "State",
46
+ "CallNext",
47
+ "TestClient",
48
+ "TestResponse",
49
+ "serve",
50
+ "to_response",
51
+ "__version__",
52
+ ]