From 842748b149022a01b54f57bc9b11a8fd7e5c1752 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 1 Jan 2026 14:15:59 -0500 Subject: [PATCH 1/7] Some docs improvements. --- docs/_static/favicon.ico | Bin 0 -> 4154 bytes docs/_static/favicon.svg | 102 --------------------------------------- docs/conf.py | 8 +-- 3 files changed, 5 insertions(+), 105 deletions(-) create mode 100644 docs/_static/favicon.ico delete mode 100644 docs/_static/favicon.svg diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..005e1b35944461b786a951ce08513e899005761a GIT binary patch literal 4154 zcmeH~drVVT9LKAfd$B13YM6^Cn9R+&WKQF7^xq*k|$_1QExrKUm+4kuTcj^9r)il z;4=1byC56WAH~Q{2a4N`JnTev4dau%psN4Lnk4%W-KX-YSiXdkg>OGpVy0};xaW~tmfS8aG^aL-JTT8rI&;UMHr6d z`28>(JLHeAr|<}!*=cAKHqw;z4MXkCj~#=vtDTl@ag@9l$%Q##T$~$D$HAZJKVOKY z{5&1$J1KehRZ14TBJ!zRUNnP>Pv+7Pw+6$}bo9CVso$^$x4AFi7~JMw>c3h_>B5Oz zSsX^`q7dr8UPj;9JnT9xHtjVLM*%gT&8KWhC}kf^5`-Nar8%7znq56~nwI7_H;ae5lOqxmimMOG-8;14bE(Wh=F;KLL zTR%>tV_PKVGx6N3JjB57NwjX7KxEw z<$i?i+Foo|cZ=r;^xK2zN`8T!v^Tg{v5TSF47yX_p(8N}{mvP9&4KHyUI)&mTwJ;n zSWdo=X@4-reJ^543&v6V3-+r?I2%$K$ce&~`Yip~)46wfv%k(!w;Nk|5v{X0*31n7%YArXH_hU{U#p6 zZ+J~5qU|Wwiw{|14r4-pzh-ApZ z)4UmP*FGeHrzsApHy^q8DDuE1?B`}NbUIwb)gG|^$N*Kc35{$#H`XDwZ9vwip!BBW zvz|sV<)Rp~9+D~Y4ACYqin3_)Hm${VVH}?N6=ELyW9dH{Bs)+Yjrc4@_)G`!8Mfi; zjK!ymM$yhgsSZJ@8vl@&Q7WHDsS)eGorvl&KAPrVD|k)Ny&1JJ9JThDf2bLQTK^Ji zXEZo#g@J^yZAO1A*zW}Ulct8LE literal 0 HcmV?d00001 diff --git a/docs/_static/favicon.svg b/docs/_static/favicon.svg deleted file mode 100644 index cce6a9bd..00000000 --- a/docs/_static/favicon.svg +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/conf.py b/docs/conf.py index 1ebdf4fa..6fb64583 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,8 +25,10 @@ html_theme = "shibuya" html_theme_options = { "accent_color": "blue", - # "light_logo": "_static/logo_black.svg", - # "dark_logo": "_static/logo_white.svg", + "light_logo": "_static/logo_black.svg", + "dark_logo": "_static/logo_white.svg", + "github_url": "https://github.com/ZeroIntensity/view.py", + "announcement": "view.py is currently in alpha and not considered ready for production", } html_static_path = ["_static"] -# html_favicon = "_static/favicon.svg" +html_favicon = "_static/favicon.ico" From 582db5ddac83cc9dfc1e6c501ad4c2bf1126a3b5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 1 Jan 2026 14:28:31 -0500 Subject: [PATCH 2/7] Add logo target to the docs. --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 6fb64583..583a7d38 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,6 +27,7 @@ "accent_color": "blue", "light_logo": "_static/logo_black.svg", "dark_logo": "_static/logo_white.svg", + "logo_target": "https://view.zintensity.dev", "github_url": "https://github.com/ZeroIntensity/view.py", "announcement": "view.py is currently in alpha and not considered ready for production", } From 6b313ee2fb9f11be7f5d47b6f06069af7b551c23 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 1 Jan 2026 14:31:05 -0500 Subject: [PATCH 3/7] Update Shibuya version. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 781e4d48..f5a7361b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ # Requirements for Netlify sphinx>=7.0 -shibuya~=2025.8 +shibuya>=2025 From 16f94a1ae6dd55eb38f13090c972ef413e1d2002 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 1 Jan 2026 14:37:05 -0500 Subject: [PATCH 4/7] Add a sidebar. --- docs/conf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 583a7d38..0425f036 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,3 +33,10 @@ } html_static_path = ["_static"] html_favicon = "_static/favicon.ico" +html_context = { + "source_type": "github", + "source_user": "ZeroIntensity", + "source_repo": "view.py", + "source_version": "main", + "source_docs_path": "/docs/", +} From 75da39f2c069136b4ab0e7b4821c1619b39e7b5f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 1 Jan 2026 15:07:31 -0500 Subject: [PATCH 5/7] Add an autogenerated API reference. --- .gitignore | 1 + docs/api.rst | 8 ++++++++ docs/conf.py | 15 ++++++++++++++- docs/index.rst | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 docs/api.rst diff --git a/.gitignore b/.gitignore index a2bd0196..c59bbbb2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ compile_flags.txt # Sphinx docs/_build/ +docs/generated/ diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 00000000..502b4c29 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,8 @@ +API Reference +============= + +.. autosummary:: + :toctree: generated + :recursive: + + view diff --git a/docs/conf.py b/docs/conf.py index 0425f036..4cffdad0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,20 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ["sphinx.ext.intersphinx"] +extensions = [ + "sphinx.ext.intersphinx", + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', +] +autosummary_generate = True +add_module_names = False # Cleaner output + +# This is the key part for making detailed pages: +autodoc_default_options = { + 'members': True, + 'undoc-members': True, + 'show-inheritance': True, +} exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} diff --git a/docs/index.rst b/docs/index.rst index 683abe85..3d69435e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,3 +6,5 @@ Nothing here yet... .. toctree:: :maxdepth: 2 :caption: Contents: + + api From b1cdb5855bc1475881d6471029999fe51696fb7f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 1 Jan 2026 15:13:02 -0500 Subject: [PATCH 6/7] Use Sphinx references in docstrings. --- src/view/core/app.py | 2 +- src/view/core/headers.py | 6 +++--- src/view/core/multi_map.py | 4 ++-- src/view/core/request.py | 4 ++-- src/view/core/response.py | 9 +++++---- src/view/dom/core.py | 2 +- src/view/exceptions.py | 4 ++-- src/view/javascript.py | 2 +- src/view/run/servers.py | 14 +++++++------- src/view/testing.py | 6 +++--- 10 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/view/core/app.py b/src/view/core/app.py index dcbd5ecd..5962808b 100644 --- a/src/view/core/app.py +++ b/src/view/core/app.py @@ -123,7 +123,7 @@ def run( This is a sort of magic function that's supposed to "just work". If finer control over the server settings is desired, explicitly use the - server's API with the app's `asgi` or `wsgi` method. + server's API with the app's :meth:`asgi` or :meth:`wsgi` method. """ from view.run.servers import ServerSettings diff --git a/src/view/core/headers.py b/src/view/core/headers.py index 35e3cdc4..e6ed2fa7 100644 --- a/src/view/core/headers.py +++ b/src/view/core/headers.py @@ -82,8 +82,8 @@ def with_new_value(self, key: str, value: str) -> HTTPHeaders: def as_real_headers(headers: HeadersLike | None, /) -> HTTPHeaders: """ - Convenience function for casting a "header-like object" (or `None`) - to a `MultiMap`. + Convenience function for casting a "header-like object" (or ``None``) + to a :class:`MultiMap`. """ if headers is None: return HTTPHeaders() @@ -111,7 +111,7 @@ def as_real_headers(headers: HeadersLike | None, /) -> HTTPHeaders: def wsgi_to_headers(environ: Mapping[str, Any]) -> HTTPHeaders: """ - Convert WSGI headers (from the `environ`) to a case-insensitive multi-map. + Convert WSGI headers (from the ``environ``) to a case-insensitive multi-map. """ values: list[tuple[LowerStr, str]] = [] diff --git a/src/view/core/multi_map.py b/src/view/core/multi_map.py index 6fbee710..8aa143cd 100644 --- a/src/view/core/multi_map.py +++ b/src/view/core/multi_map.py @@ -45,7 +45,7 @@ def __init__(self, items: Iterable[tuple[KeyT, ValueT]] = ()) -> None: def __getitem__(self, key: KeyT, /) -> ValueT: """ - Get the first value if it exists, or else raise a `KeyError`. + Get the first value if it exists, or else raise a :exc:`KeyError`. """ return self._values[key][0] @@ -127,7 +127,7 @@ def get_many(self, key: KeyT) -> Sequence[ValueT]: def get_exactly_one(self, key: KeyT) -> ValueT: """ Get precisely one value for a key. If more than one value is present, - then this raises a `HasMultipleValuesError`. + then this raises a :exc:`HasMultipleValuesError`. """ value = self._values[key] if len(value) != 1: diff --git a/src/view/core/request.py b/src/view/core/request.py index e9b3dc2e..ab6a7354 100644 --- a/src/view/core/request.py +++ b/src/view/core/request.py @@ -115,12 +115,12 @@ class Request(BodyMixin): method: Method """ - The HTTP method of the request. See `Method`. + The HTTP method of the request. See :class:`Method`. """ headers: HTTPHeaders """ - A "multi-dictionary" containing the request headers. This is `dict`-like, + A "multi-dictionary" containing the request headers. This is :class:`dict`-like, but if a header has multiple values, it is represented by a list. """ diff --git a/src/view/core/response.py b/src/view/core/response.py index b2e3e4f4..3d0bbf2e 100644 --- a/src/view/core/response.py +++ b/src/view/core/response.py @@ -94,7 +94,7 @@ def from_file( content_type: str | None = None, ) -> FileResponse: """ - Generate a `FileResponse` from a file path. + Generate a :class:`FileResponse` from a file path. """ if __debug__ and not isinstance(chunk_size, int): raise InvalidTypeError(chunk_size, int) @@ -145,7 +145,8 @@ def from_content( headers: HeadersLike | None = None, ) -> TextResponse[AnyStr]: """ - Generate a `TextResponse` from either a `str` or `bytes` object. + Generate a :class:`TextResponse` from either a :class:`str` or + :class:`bytes` object. """ if __debug__ and not isinstance(content, (str, bytes)): @@ -232,7 +233,7 @@ def _wrap_response_tuple(response: _ResponseTuple) -> Response: def _wrap_response(response: ResponseLike, /) -> Response: """ - Wrap a response from a view into a `Response` object. + Wrap a response from a view into a :class:`Response` object. """ logger.debug(f"Got response: {response!r}") if isinstance(response, Response): @@ -266,7 +267,7 @@ async def stream() -> AsyncGenerator[bytes]: async def wrap_view_result(result: ViewResult, /) -> Response: """ Turn the raw result of a view, which might be a coroutine, into a usable - `Response` object. + :class:`Response` object. """ if isinstance(result, Awaitable): result = await result diff --git a/src/view/dom/core.py b/src/view/dom/core.py index 3b97eaf5..05c032db 100644 --- a/src/view/dom/core.py +++ b/src/view/dom/core.py @@ -182,7 +182,7 @@ def html_response( function: HTMLView, ) -> RouteView: """ - Return a `Response` object from a function returning HTML. + Return a :class:`Response` object from a function returning HTML. """ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> Response: diff --git a/src/view/exceptions.py b/src/view/exceptions.py index fe610a49..928ffe73 100644 --- a/src/view/exceptions.py +++ b/src/view/exceptions.py @@ -17,8 +17,8 @@ def __init__(self, *msg: str) -> None: class InvalidTypeError(ViewError, TypeError): """ Something got a type that it didn't expect. For example, passing a - `str` object in a place where a `bytes` object was expected would raise - this error. + :class:`str` object in a place where a :class:`bytes` object was + expected would raise this error. In order to fix this, please review the documentation of the function you're attempting to call and ensure that you are passing it the correct diff --git a/src/view/javascript.py b/src/view/javascript.py index 4594f615..8fff59a7 100644 --- a/src/view/javascript.py +++ b/src/view/javascript.py @@ -20,7 +20,7 @@ @runtime_checkable class SupportsJavaScript(Protocol): """ - Protocol for objects that want to allow use in `as_javascript_expression()`. + Protocol for objects that want to allow use in :func:`as_javascript_expression`. """ def as_javascript(self) -> str: diff --git a/src/view/run/servers.py b/src/view/run/servers.py index bba566d1..b32db0d5 100644 --- a/src/view/run/servers.py +++ b/src/view/run/servers.py @@ -48,7 +48,7 @@ class ServerSettings: def run_uvicorn(self) -> None: """ - Run the app using the `uvicorn` library. + Run the app using the ``uvicorn`` library. """ import uvicorn @@ -56,7 +56,7 @@ def run_uvicorn(self) -> None: def run_hypercorn(self) -> None: """ - Run the app using the `hypercorn` library. + Run the app using the ``hypercorn`` library. """ import asyncio @@ -69,7 +69,7 @@ def run_hypercorn(self) -> None: def run_daphne(self) -> None: """ - Run the app using the `daphne` library. + Run the app using the ``daphne`` library. """ from daphne.endpoints import build_endpoint_description_strings from daphne.server import Server @@ -83,7 +83,7 @@ def run_daphne(self) -> None: def run_gunicorn(self) -> None: """ - Run the app using the `gunicorn` library. + Run the app using the ``gunicorn`` library. """ from gunicorn.app.base import BaseApplication @@ -111,7 +111,7 @@ def load(self): def run_werkzeug(self) -> None: """ - Run the app using the `werkzeug` library. + Run the app using the ``werkzeug`` library. """ from werkzeug.serving import run_simple @@ -119,7 +119,7 @@ def run_werkzeug(self) -> None: def run_wsgiref(self) -> None: """ - Run the app using the built-in `wsgiref` module. + Run the app using the built-in :mod:`wsgiref` module. """ from wsgiref.simple_server import make_server @@ -131,7 +131,7 @@ def run_app_on_any_server(self) -> None: Run the app on the nearest available ASGI or WSGI server. This will always succeed, as it will fall back to the standard - `wsgiref` module if no other server is installed. + :mod:`wsgiref` module if no other server is installed. """ servers: dict[str, StartServer] = { "uvicorn": self.run_uvicorn, diff --git a/src/view/testing.py b/src/view/testing.py index 4d986e19..24345690 100644 --- a/src/view/testing.py +++ b/src/view/testing.py @@ -21,7 +21,7 @@ async def into_tuple( ) -> tuple[bytes, int, HTTPHeaders]: """ Convenience function for transferring a test client call into a tuple - through a single ``await``. + through a single :keyword:`await`. """ response = await response_coro body = await response.body() @@ -30,7 +30,7 @@ async def into_tuple( def ok(body: str | bytes) -> tuple[bytes, int, dict[str, str]]: """ - Utility function for an OK response from `into_tuple()`. + Utility function for an OK response from :func:`into_tuple`. """ if isinstance(body, str): @@ -40,7 +40,7 @@ def ok(body: str | bytes) -> tuple[bytes, int, dict[str, str]]: def bad(status_code: int) -> tuple[bytes, int, dict[str, str]]: """ - Utility function for an error response from `into_tuple()`. + Utility function for an error response from :func:`into_tuple`. """ body = STATUS_STRINGS[status_code] return (f"{status_code} {body}".encode(), status_code, {}) From cf12753ad6eece70ee1ed15ecc43d7a2c382d758 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 3 Jan 2026 15:13:12 -0500 Subject: [PATCH 7/7] Improve and fix some docstrings. --- src/view/core/headers.py | 4 ++-- src/view/dom/core.py | 3 ++- src/view/run/asgi.py | 3 +++ src/view/run/wsgi.py | 3 +++ src/view/utils.py | 4 ++-- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/view/core/headers.py b/src/view/core/headers.py index e6ed2fa7..3d1fc3ff 100644 --- a/src/view/core/headers.py +++ b/src/view/core/headers.py @@ -126,7 +126,7 @@ def wsgi_to_headers(environ: Mapping[str, Any]) -> HTTPHeaders: return HTTPHeaders(values) -def headers_to_wsgi(headers: HTTPHeaders) -> WSGIHeaders: +def headers_to_wsgi(headers: HTTPHeaders, /) -> WSGIHeaders: """ Convert a case-insensitive multi-map to a WSGI header iterable. """ @@ -148,7 +148,7 @@ def asgi_to_headers(headers: ASGIHeaders, /) -> HTTPHeaders: lower_str = LowerStr(key.decode("utf-8")) values.append((lower_str, value.decode("utf-8"))) - return MultiMap(values) + return HTTPHeaders(values) def headers_to_asgi(headers: HTTPHeaders, /) -> ASGIHeaders: diff --git a/src/view/dom/core.py b/src/view/dom/core.py index 05c032db..f4b2f437 100644 --- a/src/view/dom/core.py +++ b/src/view/dom/core.py @@ -182,7 +182,8 @@ def html_response( function: HTMLView, ) -> RouteView: """ - Return a :class:`Response` object from a function returning HTML. + Return a :class:`~view.core.response.Response` object from a function + returning HTML. """ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> Response: diff --git a/src/view/run/asgi.py b/src/view/run/asgi.py index 734c6fd3..012419da 100644 --- a/src/view/run/asgi.py +++ b/src/view/run/asgi.py @@ -71,6 +71,9 @@ def asgi_for_app(app: BaseApp, /) -> ASGIProtocol: """ Generate an ASGI-compliant callable for a given app, allowing it to be executed in an ASGI server. + + Don't use this directly; prefer the :meth:`view.core.app.BaseApp.wsgi` + method instead. """ async def asgi( diff --git a/src/view/run/wsgi.py b/src/view/run/wsgi.py index c3e9a1c1..5ccfaa1c 100644 --- a/src/view/run/wsgi.py +++ b/src/view/run/wsgi.py @@ -32,6 +32,9 @@ def wsgi_for_app( """ Generate a WSGI-compliant callable for a given app, allowing it to be executed in an ASGI server. + + Don't use this directly; prefer the :meth:`view.core.app.BaseApp.wsgi` + method instead. """ loop = loop or asyncio.new_event_loop() diff --git a/src/view/utils.py b/src/view/utils.py index 1fb1c10d..c3fbed56 100644 --- a/src/view/utils.py +++ b/src/view/utils.py @@ -19,7 +19,7 @@ def reraise( Context manager to reraise one or many exceptions as a single exception. This is primarily useful for reraising exceptions into HTTP errors, such - as an error 400 (Bad Request). + as a :class:`~view.core.status_codes.BadRequest`. """ target = exceptions or Exception @@ -42,7 +42,7 @@ def reraises( entire function. This is primarily useful for reraising exceptions into HTTP errors, such - as an error 400 (Bad Request). + as a :class:`~view.core.status_codes.BadRequest`. """ target = exceptions or Exception