Skip to content

Commit

Permalink
Merge pull request #39 from ZeroIntensity/headers-fix
Browse files Browse the repository at this point in the history
Docs, fix #38, and fix tri responses
  • Loading branch information
ZeroIntensity authored Sep 9, 2023
2 parents e1826cc + 429fabb commit 8ce6ef7
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 41 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0-alpha3] - 2023-09-9
- Patched header responses
- Added tests for headers
- Updated repr for `Route`
- Patched responses with three values
- Documented responses and result protocol

## [1.0.0-alpha2] - 2023-09-9

- Added `App.test()`
Expand Down
48 changes: 42 additions & 6 deletions docs/components.md → docs/responses.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
# Components
# Responses

## Using built-in components
## Basics

view.py allows you to return three things from a route: the body, the headers, and the status code.

You can simply return these via a tuple:

```py
@app.get("/")
async def index():
return "you are not worthy", 400, {"x-worthiness": "0"}
```

In fact, it can be in any order you want:

```py
@app.get("/")
async def index():
return {"x-worthiness": "0"}, "you are not worthy", 400
```

### Result Protocol

If you need to return a more complicated object, you can put a `__view_result__` function on it:

```py
class MyObject:
def __view_result__(self) -> str:
return "123"

@app.get("/")
async def index():
return MyObject()
```

## Components

### Using built-in components

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

Expand Down Expand Up @@ -36,7 +72,7 @@ The above would translate to the following HTML snippet:
</html>
```

### Children
#### Children

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

Expand All @@ -54,7 +90,7 @@ Would translate to:
</div>
```

## Attributes
### Attributes

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

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

### Classes
#### Classes

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

```py
div(cls="hello")
```

## Custom Components
### Custom Components

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

Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ nav:
- Home: index.md
- Creating an app: creating.md
- Running: running.md
- Components: components.md
- Responses: responses.md
- Parameters: parameters.md

theme:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
data = toml.load(f)
setup(
name="view.py",
version="1.0.0-alpha2",
version="1.0.0-alpha3",
packages=["view"],
project_urls=data["project"]["urls"],
package_dir={"": "src"},
Expand Down
64 changes: 38 additions & 26 deletions src/_view/app.c
Original file line number Diff line number Diff line change
Expand Up @@ -853,75 +853,87 @@ static int find_result_for(
target,
item
);

if (!v) {
Py_DECREF(iter);
return -1;
}
PyObject* v_str = PyObject_Str(v);
if (v_str) {

const char* v_str = PyUnicode_AsUTF8(v);
if (!v_str) {
Py_DECREF(iter);
return -1;
}

PyObject* item_str = PyObject_Str(item);
if (!item_str) {
Py_DECREF(v_str);
Py_DECREF(iter);
return -1;
}

PyObject* v_bytes = PyBytes_FromObject(v_str);
if (!v_bytes) {
Py_DECREF(v_str);
const char* item_cc = PyUnicode_AsUTF8(item_str);

if (!item_cc) {
Py_DECREF(iter);
Py_DECREF(item_str);
return -1;
}
PyObject* item_bytes = PyBytes_FromObject(item_str);

PyObject* item_bytes = PyBytes_FromString(item_cc);
Py_DECREF(item_str);

if (!item_bytes) {
Py_DECREF(v_bytes);
Py_DECREF(v_str);
Py_DECREF(iter);
Py_DECREF(item_str);
return -1;
}

PyObject* header_list = PyList_New(2);
if (PyList_Append(
PyObject* header_list = PyTuple_New(2);

if (!header_list) {
Py_DECREF(iter);
Py_DECREF(item_bytes);
return -1;
}

if (PyTuple_SetItem(
header_list,
0,
item_bytes
) < 0) {
Py_DECREF(header_list);
Py_DECREF(item_str);
Py_DECREF(iter);
Py_DECREF(v_str);
Py_DECREF(item_bytes);
Py_DECREF(v_bytes);
};

if (PyList_Append(
Py_DECREF(item_bytes);

PyObject* v_bytes = PyBytes_FromString(v_str);

if (!v_bytes) {
Py_DECREF(header_list);
Py_DECREF(iter);
return -1;
}

if (PyTuple_SetItem(
header_list,
1,
v_bytes
) < 0) {
Py_DECREF(header_list);
Py_DECREF(item_str);
Py_DECREF(iter);
Py_DECREF(v_str);
Py_DECREF(item_bytes);
Py_DECREF(v_bytes);
};

Py_DECREF(item_str);
Py_DECREF(v_str);
Py_DECREF(item_bytes);
Py_DECREF(v_bytes);

if (PyList_Append(
headers,
header_list
) < 0) {
Py_DECREF(header_list);
Py_DECREF(iter);
return -1;
}
Py_DECREF(header_list);
}

Py_DECREF(iter);
Expand Down Expand Up @@ -998,8 +1010,8 @@ static int handle_result(
headers
) < 0) return -1;

if (second && find_result_for(
second,
if (third && find_result_for(
third,
&res_str,
&status,
headers
Expand Down
12 changes: 8 additions & 4 deletions src/_view/awaitable.c
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,12 @@ gen_next(PyObject *self)

Py_INCREF(aw);
if (cb->callback((PyObject *) aw, value) < 0) {
PyErr_Restore(type, value, traceback);
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_SystemError, "callback returned -1 without exception set");
return NULL;
}
if (fire_err_callback((PyObject *) aw, g->gw_current_await, cb) < 0) {
PyErr_Restore(type, value, traceback);
return NULL;
}
}
Expand Down Expand Up @@ -328,7 +332,7 @@ awaitable_dealloc(PyObject *self)
for (int i = 0; i < aw->aw_callback_size; i++) {
awaitable_callback *cb = aw->aw_callbacks[i];
if (!cb->done) Py_DECREF(cb->coro);
PyMem_Free(cb);
free(cb);
}

if (aw->aw_arb_values) PyMem_Free(aw->aw_arb_values);
Expand Down Expand Up @@ -464,7 +468,7 @@ PyAwaitable_AddAwait(
Py_INCREF(aw);
PyAwaitableObject *a = (PyAwaitableObject *) aw;

awaitable_callback *aw_c = PyMem_Malloc(sizeof(awaitable_callback));
awaitable_callback *aw_c = malloc(sizeof(awaitable_callback));
if (aw_c == NULL) {
Py_DECREF(aw);
Py_DECREF(coro);
Expand All @@ -486,7 +490,7 @@ PyAwaitable_AddAwait(
--a->aw_callback_size;
Py_DECREF(aw);
Py_DECREF(coro);
PyMem_Free(aw_c);
free(aw_c);
PyErr_NoMemory();
return -1;
}
Expand Down
2 changes: 1 addition & 1 deletion src/view/__about__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = "1.0.0-alpha2"
__version__ = "1.0.0-alpha3"
__license__ = "MIT"
5 changes: 4 additions & 1 deletion src/view/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ async def receive():
async def send(obj: dict[str, Any]):
if obj["type"] == "http.response.start":
await start.put(
({k: v for k, v in obj["headers"]}, obj["status"])
(
{k.decode(): v.decode() for k, v in obj["headers"]},
obj["status"],
)
)
elif obj["type"] == "http.response.body":
await body_q.put(obj["body"].decode())
Expand Down
2 changes: 1 addition & 1 deletion src/view/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def wrapper(handler: ViewRoute):
return wrapper

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

__str__ = __repr__

Expand Down
50 changes: 50 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,53 @@ async def index():
assert res.status == 400
assert res.message == "error"

@test("headers")
async def _():
app = new_app()

@app.get("/")
async def index():
return "hello", {"a": "b"}

async with app.test() as test:
res = await test.get("/")
assert res.headers["a"] == "b"
assert res.message == "hello"


@test("combination of headers, responses, and status codes")
async def _():
app = new_app()

@app.get("/")
async def index():
return 201, "123", {"a": "b"}

async with app.test() as test:
res = await test.get("/")
assert res.status == 201
assert res.message == "123"
assert res.headers["a"] == "b"


@test("result protocol")
async def _():
app = new_app()

class MyObject:
def __view_result__(self) -> str:
return "hello"

@app.get("/")
async def index():
return MyObject()

@app.get("/multi")
async def multi():
return 201, MyObject()

async with app.test() as test:
assert (await test.get("/")).message == "hello"
res = await test.get("/multi")
assert res.message == "hello"
assert res.status == 201

0 comments on commit 8ce6ef7

Please sign in to comment.