Skip to content

Returning Responses

Basic Responses

In any web framework, returning a response can be as simple as returning a string of text or quite complex with all sorts of things like server-side rendering. Right out of the box, View supports returning status codes, headers, and a response without any fancy tooling. A response must contain a body (this is a str), but may also contain a status (int) or headers (dict[str, str]). These may be in any order.

from view import new_app

app = new_app()

@app.get("/")
async def index():
    return "Hello, view.py", 201, {"x-my-header": "my_header"}

Response Protocol

If you have some sort of object that you want to wrap a response around, view.py gives you the __view_response__ protocol. The only requirements are:

  • __view_response__ is available on the returned object (doesn't matter if it's static or instance)
  • __view_response__ returns data that corresponds to the allowed return values.

For example, a type MyObject defining __view_response__ could look like:

from view import new_app

app = new_app()

class MyObject:
    def __view_response__(self):
        return "Hello from MyObject!", {"x-www-myobject": "foo"}

@app.get("/")
async def index():
    return MyObject()  # this is ok

app.run()

Note that in the above scenario, you wouldn't actually need a whole object. Instead, you could also just define a utility function:

def _response():
    return "Hello, view.py!", {"foo": "bar"}

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

Response Objects

View comes with two built in response objects: Response and HTML.

  • Response is simply a wrapper around other responses.
  • HTML is for returning HTML content.

Bases: Generic[T]

Wrapper for responses.

Source code in src/view/response.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
class Response(Generic[T]):
    """Wrapper for responses."""

    def __init__(
        self,
        body: T | None = None,
        status: int = 200,
        headers: dict[str, str] | None = None,
        *,
        body_translate: BodyTranslateStrategy | None = _Find,
    ) -> None:
        self.body = body
        self.status = status
        self.headers = headers or {}
        self._raw_headers: list[tuple[bytes, bytes]] = []
        if body_translate:
            self.translate = body_translate
        else:
            self.translate = (
                "str" if not hasattr(body, "__view_result__") else "result"
            )

    def cookie(
        self,
        key: str,
        value: str = "",
        *,
        max_age: int | None = None,
        expires: int | DateTime | None = None,
        path: str | None = None,
        domain: str | None = None,
        http_only: bool = False,
        same_site: SameSite = "lax",
        partitioned: bool = False,
        secure: bool = False,
    ) -> None:
        """Set a cookie.

        Args:
            key: Cookie name.
            value: Cookie value.
            max_age: Max age of the cookies.
            expires: When the cookie expires.
            domain: Domain the cookie is valid at.
            http_only: Whether the cookie should be HTTP only.
            same_site: SameSite setting for the cookie.
            partitioned: Whether to tie it to the top level site.
            secure: Whether the cookie should enforce HTTPS."""
        cookie_str = f"{key}={value}; SameSite={same_site}".encode()

        if expires:
            dt = (
                expires
                if isinstance(expires, DateTime)
                else DateTime.fromtimestamp(expires)
            )
            ts = timestamp(dt)
            cookie_str += f"; Expires={ts}".encode()

        if http_only:
            cookie_str += b"; HttpOnly"

        if domain:
            cookie_str += f"; Domain={domain}".encode()

        if max_age:
            cookie_str += f"; Max-Age={max_age}".encode()

        if partitioned:
            cookie_str += b"; Partitioned"

        if secure:
            cookie_str += b"; Secure"

        if path:
            cookie_str += f"; Path={path}".encode()

        self._raw_headers.append((b"Set-Cookie", cookie_str))

    def _build_headers(self) -> tuple[tuple[bytes, bytes], ...]:
        headers: list[tuple[bytes, bytes]] = [*self._raw_headers]

        for k, v in self.headers.items():
            headers.append((k.encode(), v.encode()))

        return tuple(headers)

    def __view_result__(self):
        body: str = ""
        if self.translate == "str":
            body = str(self.body)
        elif self.translate == "repr":
            body = repr(self.body)
        else:
            view_result = getattr(self.body, "__view_result__", None)

            if not view_result:
                raise AttributeError(f"{self.body!r} has no __view_result__")

            body_res = view_result()
            if isinstance(body_res, str):
                body = body_res
            else:
                for i in body_res:
                    if isinstance(i, str):
                        body = i

        return body, self.status, self._build_headers()

view.response.Response.cookie(key: str, value: str = '', *, max_age: int | None = None, expires: int | DateTime | None = None, path: str | None = None, domain: str | None = None, http_only: bool = False, same_site: SameSite = 'lax', partitioned: bool = False, secure: bool = False) -> None

Set a cookie.

Parameters:

Name Type Description Default
key str

Cookie name.

required
value str

Cookie value.

''
max_age int | None

Max age of the cookies.

None
expires int | datetime | None

When the cookie expires.

None
domain str | None

Domain the cookie is valid at.

None
http_only bool

Whether the cookie should be HTTP only.

False
same_site SameSite

SameSite setting for the cookie.

'lax'
partitioned bool

Whether to tie it to the top level site.

False
secure bool

Whether the cookie should enforce HTTPS.

False
Source code in src/view/response.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def cookie(
    self,
    key: str,
    value: str = "",
    *,
    max_age: int | None = None,
    expires: int | DateTime | None = None,
    path: str | None = None,
    domain: str | None = None,
    http_only: bool = False,
    same_site: SameSite = "lax",
    partitioned: bool = False,
    secure: bool = False,
) -> None:
    """Set a cookie.

    Args:
        key: Cookie name.
        value: Cookie value.
        max_age: Max age of the cookies.
        expires: When the cookie expires.
        domain: Domain the cookie is valid at.
        http_only: Whether the cookie should be HTTP only.
        same_site: SameSite setting for the cookie.
        partitioned: Whether to tie it to the top level site.
        secure: Whether the cookie should enforce HTTPS."""
    cookie_str = f"{key}={value}; SameSite={same_site}".encode()

    if expires:
        dt = (
            expires
            if isinstance(expires, DateTime)
            else DateTime.fromtimestamp(expires)
        )
        ts = timestamp(dt)
        cookie_str += f"; Expires={ts}".encode()

    if http_only:
        cookie_str += b"; HttpOnly"

    if domain:
        cookie_str += f"; Domain={domain}".encode()

    if max_age:
        cookie_str += f"; Max-Age={max_age}".encode()

    if partitioned:
        cookie_str += b"; Partitioned"

    if secure:
        cookie_str += b"; Secure"

    if path:
        cookie_str += f"; Path={path}".encode()

    self._raw_headers.append((b"Set-Cookie", cookie_str))

Bases: Response[str]

HTML response wrapper.

Source code in src/view/response.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
class HTML(Response[str]):
    """HTML response wrapper."""

    def __init__(
        self,
        body: TextIO | str | Path | DOMNode,
        status: int = 200,
        headers: dict[str, str] | None = None,
    ) -> None:
        parsed_body = ""

        if isinstance(body, Path):
            parsed_body = body.read_text()
        elif isinstance(body, str):
            parsed_body = body
        elif isinstance(body, DOMNode):
            parsed_body = body.data
        else:
            try:
                parsed_body = body.read()
            except AttributeError:
                raise TypeError(
                    f"expected TextIO, str, Path, or DOMNode, not {type(body)}",  # noqa
                ) from None

        super().__init__(parsed_body, status, headers)
        self._raw_headers.append((b"content-type", b"text/html"))

A common use case for Response is wrapping an object that has a __view_response__ and changing one of the values. For example:

from view import new_app, Response

app = new_app()

class Test:
    def __view_result__(self):
        return "test", 201

@app.get("/")
async def index():
    return Response(Test(), status=200)  # 200 is returned, not 201

app.run()

Another common case for Response is using cookies. You can add a cookie to the response via the cookie method:

@app.get("/")
async def index():
    res = Response(...)
    res.cookie("hello", "world")
    return res

Set a cookie.

Parameters:

Name Type Description Default
key str

Cookie name.

required
value str

Cookie value.

''
max_age int | None

Max age of the cookies.

None
expires int | datetime | None

When the cookie expires.

None
domain str | None

Domain the cookie is valid at.

None
http_only bool

Whether the cookie should be HTTP only.

False
same_site SameSite

SameSite setting for the cookie.

'lax'
partitioned bool

Whether to tie it to the top level site.

False
secure bool

Whether the cookie should enforce HTTPS.

False
Source code in src/view/response.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def cookie(
    self,
    key: str,
    value: str = "",
    *,
    max_age: int | None = None,
    expires: int | DateTime | None = None,
    path: str | None = None,
    domain: str | None = None,
    http_only: bool = False,
    same_site: SameSite = "lax",
    partitioned: bool = False,
    secure: bool = False,
) -> None:
    """Set a cookie.

    Args:
        key: Cookie name.
        value: Cookie value.
        max_age: Max age of the cookies.
        expires: When the cookie expires.
        domain: Domain the cookie is valid at.
        http_only: Whether the cookie should be HTTP only.
        same_site: SameSite setting for the cookie.
        partitioned: Whether to tie it to the top level site.
        secure: Whether the cookie should enforce HTTPS."""
    cookie_str = f"{key}={value}; SameSite={same_site}".encode()

    if expires:
        dt = (
            expires
            if isinstance(expires, DateTime)
            else DateTime.fromtimestamp(expires)
        )
        ts = timestamp(dt)
        cookie_str += f"; Expires={ts}".encode()

    if http_only:
        cookie_str += b"; HttpOnly"

    if domain:
        cookie_str += f"; Domain={domain}".encode()

    if max_age:
        cookie_str += f"; Max-Age={max_age}".encode()

    if partitioned:
        cookie_str += b"; Partitioned"

    if secure:
        cookie_str += b"; Secure"

    if path:
        cookie_str += f"; Path={path}".encode()

    self._raw_headers.append((b"Set-Cookie", cookie_str))

Note that all response classes inherit from Response, meaning you can use this functionality anywhere.

Review

Responses can be returned with a string, integer, and/or dictionary in any order.

  • The string represents the body of the response (e.g. the HTML or JSON)
  • The integer represents the status code (200 by default)
  • The dictionary represents the headers (e.g. {"x-www-my-header": "some value"})

Response objects can also be returned, which implement the __view_response__ protocol. All response classes inherit from Response, which supports operations like setting cookies.