Skip to content

Commit 8ce6ef7

Browse files
Merge pull request #39 from ZeroIntensity/headers-fix
Docs, fix #38, and fix tri responses
2 parents e1826cc + 429fabb commit 8ce6ef7

File tree

10 files changed

+153
-41
lines changed

10 files changed

+153
-41
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.0-alpha3] - 2023-09-9
9+
- Patched header responses
10+
- Added tests for headers
11+
- Updated repr for `Route`
12+
- Patched responses with three values
13+
- Documented responses and result protocol
14+
815
## [1.0.0-alpha2] - 2023-09-9
916

1017
- Added `App.test()`

docs/components.md renamed to docs/responses.md

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,42 @@
1-
# Components
1+
# Responses
22

3-
## Using built-in components
3+
## Basics
4+
5+
view.py allows you to return three things from a route: the body, the headers, and the status code.
6+
7+
You can simply return these via a tuple:
8+
9+
```py
10+
@app.get("/")
11+
async def index():
12+
return "you are not worthy", 400, {"x-worthiness": "0"}
13+
```
14+
15+
In fact, it can be in any order you want:
16+
17+
```py
18+
@app.get("/")
19+
async def index():
20+
return {"x-worthiness": "0"}, "you are not worthy", 400
21+
```
22+
23+
### Result Protocol
24+
25+
If you need to return a more complicated object, you can put a `__view_result__` function on it:
26+
27+
```py
28+
class MyObject:
29+
def __view_result__(self) -> str:
30+
return "123"
31+
32+
@app.get("/")
33+
async def index():
34+
return MyObject()
35+
```
36+
37+
## Components
38+
39+
### Using built-in components
440

541
You can import any standard HTML components from the `view.components` module:
642

@@ -36,7 +72,7 @@ The above would translate to the following HTML snippet:
3672
</html>
3773
```
3874

39-
### Children
75+
#### Children
4076

4177
You can pass an infinite number of children to a component, and it will be translated to the proper HTML:
4278

@@ -54,7 +90,7 @@ Would translate to:
5490
</div>
5591
```
5692

57-
## Attributes
93+
### Attributes
5894

5995
All built in components come with their respected attributes, per the HTML specification:
6096

@@ -64,15 +100,15 @@ async def index():
64100
return html(lang="en")
65101
```
66102

67-
### Classes
103+
#### Classes
68104

69105
Since the `class` keyword is reserved in Python, view.py uses the parameter name `cls` instead:
70106

71107
```py
72108
div(cls="hello")
73109
```
74110

75-
## Custom Components
111+
### Custom Components
76112

77113
There's no need for any fancy mechanics when making a custom component, so you can just use a normal function:
78114

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ nav:
77
- Home: index.md
88
- Creating an app: creating.md
99
- Running: running.md
10-
- Components: components.md
10+
- Responses: responses.md
1111
- Parameters: parameters.md
1212

1313
theme:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
data = toml.load(f)
1212
setup(
1313
name="view.py",
14-
version="1.0.0-alpha2",
14+
version="1.0.0-alpha3",
1515
packages=["view"],
1616
project_urls=data["project"]["urls"],
1717
package_dir={"": "src"},

src/_view/app.c

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -853,75 +853,87 @@ static int find_result_for(
853853
target,
854854
item
855855
);
856+
856857
if (!v) {
857858
Py_DECREF(iter);
858859
return -1;
859860
}
860-
PyObject* v_str = PyObject_Str(v);
861-
if (v_str) {
861+
862+
const char* v_str = PyUnicode_AsUTF8(v);
863+
if (!v_str) {
862864
Py_DECREF(iter);
863865
return -1;
864866
}
867+
865868
PyObject* item_str = PyObject_Str(item);
866869
if (!item_str) {
867-
Py_DECREF(v_str);
868870
Py_DECREF(iter);
869871
return -1;
870872
}
871873

872-
PyObject* v_bytes = PyBytes_FromObject(v_str);
873-
if (!v_bytes) {
874-
Py_DECREF(v_str);
874+
const char* item_cc = PyUnicode_AsUTF8(item_str);
875+
876+
if (!item_cc) {
875877
Py_DECREF(iter);
876-
Py_DECREF(item_str);
877878
return -1;
878879
}
879-
PyObject* item_bytes = PyBytes_FromObject(item_str);
880+
881+
PyObject* item_bytes = PyBytes_FromString(item_cc);
882+
Py_DECREF(item_str);
883+
880884
if (!item_bytes) {
881-
Py_DECREF(v_bytes);
882-
Py_DECREF(v_str);
883885
Py_DECREF(iter);
884-
Py_DECREF(item_str);
885886
return -1;
886887
}
887888

888-
PyObject* header_list = PyList_New(2);
889-
if (PyList_Append(
889+
PyObject* header_list = PyTuple_New(2);
890+
891+
if (!header_list) {
892+
Py_DECREF(iter);
893+
Py_DECREF(item_bytes);
894+
return -1;
895+
}
896+
897+
if (PyTuple_SetItem(
890898
header_list,
899+
0,
891900
item_bytes
892901
) < 0) {
893902
Py_DECREF(header_list);
894-
Py_DECREF(item_str);
895903
Py_DECREF(iter);
896-
Py_DECREF(v_str);
897904
Py_DECREF(item_bytes);
898-
Py_DECREF(v_bytes);
899905
};
900906

901-
if (PyList_Append(
907+
Py_DECREF(item_bytes);
908+
909+
PyObject* v_bytes = PyBytes_FromString(v_str);
910+
911+
if (!v_bytes) {
912+
Py_DECREF(header_list);
913+
Py_DECREF(iter);
914+
return -1;
915+
}
916+
917+
if (PyTuple_SetItem(
902918
header_list,
919+
1,
903920
v_bytes
904921
) < 0) {
905922
Py_DECREF(header_list);
906-
Py_DECREF(item_str);
907923
Py_DECREF(iter);
908-
Py_DECREF(v_str);
909-
Py_DECREF(item_bytes);
910-
Py_DECREF(v_bytes);
911924
};
912925

913-
Py_DECREF(item_str);
914-
Py_DECREF(v_str);
915-
Py_DECREF(item_bytes);
916926
Py_DECREF(v_bytes);
917927

918928
if (PyList_Append(
919929
headers,
920930
header_list
921931
) < 0) {
922932
Py_DECREF(header_list);
933+
Py_DECREF(iter);
923934
return -1;
924935
}
936+
Py_DECREF(header_list);
925937
}
926938

927939
Py_DECREF(iter);
@@ -998,8 +1010,8 @@ static int handle_result(
9981010
headers
9991011
) < 0) return -1;
10001012

1001-
if (second && find_result_for(
1002-
second,
1013+
if (third && find_result_for(
1014+
third,
10031015
&res_str,
10041016
&status,
10051017
headers

src/_view/awaitable.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,12 @@ gen_next(PyObject *self)
275275

276276
Py_INCREF(aw);
277277
if (cb->callback((PyObject *) aw, value) < 0) {
278-
PyErr_Restore(type, value, traceback);
278+
if (!PyErr_Occurred()) {
279+
PyErr_SetString(PyExc_SystemError, "callback returned -1 without exception set");
280+
return NULL;
281+
}
279282
if (fire_err_callback((PyObject *) aw, g->gw_current_await, cb) < 0) {
283+
PyErr_Restore(type, value, traceback);
280284
return NULL;
281285
}
282286
}
@@ -328,7 +332,7 @@ awaitable_dealloc(PyObject *self)
328332
for (int i = 0; i < aw->aw_callback_size; i++) {
329333
awaitable_callback *cb = aw->aw_callbacks[i];
330334
if (!cb->done) Py_DECREF(cb->coro);
331-
PyMem_Free(cb);
335+
free(cb);
332336
}
333337

334338
if (aw->aw_arb_values) PyMem_Free(aw->aw_arb_values);
@@ -464,7 +468,7 @@ PyAwaitable_AddAwait(
464468
Py_INCREF(aw);
465469
PyAwaitableObject *a = (PyAwaitableObject *) aw;
466470

467-
awaitable_callback *aw_c = PyMem_Malloc(sizeof(awaitable_callback));
471+
awaitable_callback *aw_c = malloc(sizeof(awaitable_callback));
468472
if (aw_c == NULL) {
469473
Py_DECREF(aw);
470474
Py_DECREF(coro);
@@ -486,7 +490,7 @@ PyAwaitable_AddAwait(
486490
--a->aw_callback_size;
487491
Py_DECREF(aw);
488492
Py_DECREF(coro);
489-
PyMem_Free(aw_c);
493+
free(aw_c);
490494
PyErr_NoMemory();
491495
return -1;
492496
}

src/view/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = "1.0.0-alpha2"
1+
__version__ = "1.0.0-alpha3"
22
__license__ = "MIT"

src/view/app.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ async def receive():
9595
async def send(obj: dict[str, Any]):
9696
if obj["type"] == "http.response.start":
9797
await start.put(
98-
({k: v for k, v in obj["headers"]}, obj["status"])
98+
(
99+
{k.decode(): v.decode() for k, v in obj["headers"]},
100+
obj["status"],
101+
)
99102
)
100103
elif obj["type"] == "http.response.body":
101104
await body_q.put(obj["body"].decode())

src/view/routing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def wrapper(handler: ViewRoute):
7272
return wrapper
7373

7474
def __repr__(self):
75-
return f"Route({self.method.name} {self.path or '/???'})" # noqa
75+
return f"Route({self.method.name}(\"{self.path or '/???'}\"))" # noqa
7676

7777
__str__ = __repr__
7878

tests/test_app.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,53 @@ async def index():
2828
assert res.status == 400
2929
assert res.message == "error"
3030

31+
@test("headers")
32+
async def _():
33+
app = new_app()
34+
35+
@app.get("/")
36+
async def index():
37+
return "hello", {"a": "b"}
38+
39+
async with app.test() as test:
40+
res = await test.get("/")
41+
assert res.headers["a"] == "b"
42+
assert res.message == "hello"
43+
44+
45+
@test("combination of headers, responses, and status codes")
46+
async def _():
47+
app = new_app()
48+
49+
@app.get("/")
50+
async def index():
51+
return 201, "123", {"a": "b"}
52+
53+
async with app.test() as test:
54+
res = await test.get("/")
55+
assert res.status == 201
56+
assert res.message == "123"
57+
assert res.headers["a"] == "b"
58+
59+
60+
@test("result protocol")
61+
async def _():
62+
app = new_app()
63+
64+
class MyObject:
65+
def __view_result__(self) -> str:
66+
return "hello"
67+
68+
@app.get("/")
69+
async def index():
70+
return MyObject()
71+
72+
@app.get("/multi")
73+
async def multi():
74+
return 201, MyObject()
75+
76+
async with app.test() as test:
77+
assert (await test.get("/")).message == "hello"
78+
res = await test.get("/multi")
79+
assert res.message == "hello"
80+
assert res.status == 201

0 commit comments

Comments
 (0)