Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ dist/
*.egg-info
build/
*.so
html/dist.css
html/dist.css
38venv
39venv
311venv
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ 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).

## Unreleased
## [1.0.0-alpha5] - 2023-09-24

- Added `app.query` and `app.body`
- Patched warning with starting app from incorrect filename
- Updated `__all__` for `routing.py`
- Added `view.Response` and `view.HTML`
- Fixed `__view_result__`
- Added support for `__view_body__` and `__view_construct__`
- Added support for Pydantic, `NamedTuple`, and dataclasses for type validation
- Support for direct union types (i.e. `str | int`, `Union[str, int]`) on type validation
- Added support for non async routes

## [1.0.0-alpha4] - 2023-09-10
Expand Down
142 changes: 139 additions & 3 deletions docs/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ view.py will ensure that the type sent to the server is compatible with what you

```py
@app.get("/")
@query("number", int)
@app.query("number", int)
async def index(number: int):
# number will always be an int.
# if it isn't, an error 400 is sent back to the user automatically
Expand All @@ -94,12 +94,16 @@ The following types are supported:
- `float`
- `dict` (or `typing.Dict`)
- `None`
- Pydantic Models
- Dataclasses
- `typing.TypedDict`
- `NamedTuple`

You can allow unions by just passing more parameters:

```py
@app.get('/hello')
@query("name", str, None)
@app.query("name", str, None)
async def hello(name: str | None):
if not name:
return "hello world"
Expand All @@ -111,11 +115,143 @@ You can pass type arguments to a `dict`, which are also validated by the server:

```py
@app.get("/something")
@body("data", dict[str, int]) # typing.Dict on 3.8 and 3.9
@app.body("data", dict[str, int]) # typing.Dict on 3.8 and 3.9
async def something(data: dict[str, int]):
# data will always be a dictionary of strings and integers
return "..."
```

The key in a dictionary must always be `str` (i.e. `dict[int, str]` is not allowed), but the value can be any supported type (including other dictionaries!)

### Objects

Here's an example of using an object type with `dataclasses`:

```py
from view import new_app, query
from dataclasses import dataclass, field

app = new_app()

def now() -> str:
... # Calculate current time

@dataclass
class Post:
content: str
created_at = field(default_factory=now)


@app.post("/create")
@app.query("data", Post)
async def create(data: Post):
print(f"Created post {data.created_at}")
return "Success", 201
```

You may also have recursive types, like so:

```py
class MyOtherObject(NamedTuple):
something: int

class MyObject(NamedTuple):
something: str
another_thing: MyOtherObject
```

### Typed Dictionaries

You may use `typing.TypedDict` to type your dictionary inputs if you don't want to use a basic `dict[..., ...]` (or `typing.Dict`), like so:

```py
from view import new_app
from typing import TypedDict

app = new_app()

class MyDict(TypedDict):
a: str
b: int

@app.get("/")
@app.query("data", MyDict)
async def index(data: MyDict):
return data["a"]

app.run()
```

You may also use `NotRequired` to allow certain keys to get omitted:

```py
class MyDict(TypedDict):
a: str
b: NotRequired[int]
```

## View Body Protocol

If you would like to create your own object that gets validated by view.py, you may use the `__view_body__` protocol.

A `__view_body__` should contain a dictionary containing the keys and their corresponding types, like so:

```py
from view import new_app

app = new_app()

class MyObject:
__view_body__ = {"a": str, "b": int}

@app.get("/")
@app.query("data", MyObject)
async def index(data: MyObject):
...

app.run()
```

The above would ensure the body contains something like the following in JSON:

```json
{
"data": {
"a": "...",
"b": 0
}
}
```

A default type can be annotated via `view.BodyParam`:

```py
class MyObject:
__view_body__ = {
"hello": BodyParam(types=(str, int), default="world"),
"world": BodyParam(types=str, default="hello"),
}
```

Note that `__view_body__` can also be a static function, like so:

```py
class MyObject:
@staticmethod
def __view_body__():
return {"a": str, "b": int}
```

### Initialization

By default, an object supporting `__view_body__` will have the proper keyword arguments passed to it's `__init__`.

If you would like to have special behavior in your `__init__`, you may instead add a static `__view_construct__` function that returns an instance:

```py
class MyObject:
__view_body__ = {"a": str, "b": int}

def __view_construct__(**kwargs):
return MyObject()
```
7 changes: 0 additions & 7 deletions docs/running.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
# Running

Is your view app running, you better go catch it!

!!! danger "Apologies"

I'm sorry, you may make a PR to delete this.


## Serving

You can simply run a view app via the `view serve` command, but if you're like me and would rather to just use the `python3` command, that's just as easy.
Expand Down
Loading