Skip to content

Redirects #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

- Added the `view docs` command
- Added the `redirect` function

## [1.0.0-alpha9] - 2024-2-4

Expand Down
4 changes: 1 addition & 3 deletions _view.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ class ViewApp:
def _supply_parsers(self, query: __Parser, json: __Parser, /) -> None: ...
def _register_error(self, error: type, /) -> None: ...

def test_awaitable(
coro: __Coroutine[__Any, __Any, __T], /
) -> __Awaitable[__T]: ...
def test_awaitable(coro: __Coroutine[__Any, __Any, __T], /) -> __Awaitable[__T]: ...

class Context:
def __init__(self) -> __NoReturn: ...
Expand Down
4 changes: 1 addition & 3 deletions src/view/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
try:
import _view
except ImportError as e:
raise ImportError(
"_view has not been built, did you forget to compile it?"
) from e
raise ImportError("_view has not been built, did you forget to compile it?") from e

from _view import Context # re-export
from _view import InvalidStatusError # re-export
Expand Down
7 changes: 2 additions & 5 deletions src/view/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@
import venv as _venv
from inspect import iscoroutine
from pathlib import Path
from typing import TYPE_CHECKING, NoReturn

if TYPE_CHECKING:
from .routing import Route
from typing import NoReturn

import click

Expand Down Expand Up @@ -466,7 +463,7 @@ async def index():
path_type=Path,
writable=True,
),
default=Path.cwd() / "docs.md"
default=Path.cwd() / "docs.md",
)
@click.option("--app", "-a", type=str, default=None)
def docs(file: Path, app: str | None):
Expand Down
35 changes: 23 additions & 12 deletions src/view/_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@
import warnings
from dataclasses import _MISSING_TYPE, Field, dataclass
from pathlib import Path
from typing import (TYPE_CHECKING, ForwardRef, Iterable, NamedTuple, TypedDict,
get_args, get_type_hints)
from typing import (
TYPE_CHECKING,
ForwardRef,
Iterable,
NamedTuple,
TypedDict,
get_args,
get_type_hints,
)

from _view import Context

Expand All @@ -24,10 +31,13 @@ def _eval_type(*args) -> Any:

from ._logging import Internal
from ._util import docs_hint, is_annotated, is_union, set_load
from .exceptions import (DuplicateRouteError, InvalidBodyError,
InvalidRouteError, LoaderWarning)
from .routing import (BodyParam, Method, Route, RouteData, RouteInput,
_NoDefault)
from .exceptions import (
DuplicateRouteError,
InvalidBodyError,
InvalidRouteError,
LoaderWarning,
)
from .routing import BodyParam, Method, Route, RouteData, RouteInput, _NoDefault
from .typing import Any, RouteInputDict, TypeInfo, ValueType

ExtNotRequired = None
Expand Down Expand Up @@ -100,6 +110,7 @@ def _eval_type(*args) -> Any:
*more to be added later*
"""


class _ViewNotRequired:
__VIEW_NOREQ__ = 1

Expand Down Expand Up @@ -421,7 +432,6 @@ def finalize(routes: list[Route], app: ViewApp):
target = targets[route.method]
else:
target = None


if (not route.path) and (not route.parts):
raise InvalidRouteError(f"{route} did not specify a path")
Expand Down Expand Up @@ -466,14 +476,16 @@ def finalize(routes: list[Route], app: ViewApp):
default,
None,
[],
)
),
)
index += 1

if len(route.inputs) != len(sig.parameters):
raise InvalidRouteError(
"mismatch in parameter names with automatic route inputs",
hint=docs_hint("https://view.zintensity.dev/building-projects/parameters/#automatically")
hint=docs_hint(
"https://view.zintensity.dev/building-projects/parameters/#automatically"
),
)

app.loaded_routes.append(route)
Expand All @@ -485,7 +497,7 @@ def finalize(routes: list[Route], app: ViewApp):
_format_inputs(route.inputs),
route.errors or {},
route.parts, # type: ignore
[i for i in reversed(route.middleware_funcs)]
[i for i in reversed(route.middleware_funcs)],
)
else:
for i in (route.method_list) or targets.keys():
Expand All @@ -497,11 +509,10 @@ def finalize(routes: list[Route], app: ViewApp):
_format_inputs(route.inputs),
route.errors or {},
route.parts, # type: ignore
[i for i in reversed(route.middleware_funcs)]
[i for i in reversed(route.middleware_funcs)],
)



def load_fs(app: ViewApp, target_dir: Path) -> None:
"""Filesystem loading implementation.
Similiar to NextJS's routing system. You take `target_dir` and search it,
Expand Down
3 changes: 1 addition & 2 deletions src/view/_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
from rich.live import Live
from rich.logging import RichHandler
from rich.panel import Panel
from rich.progress import (BarColumn, Progress, Task, TaskProgressColumn,
TextColumn)
from rich.progress import BarColumn, Progress, Task, TaskProgressColumn, TextColumn
from rich.progress_bar import ProgressBar
from rich.table import Table
from rich.text import Text
Expand Down
1 change: 1 addition & 0 deletions src/view/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def shell_hint(*commands: str) -> Panel:
def docs_hint(url: str) -> str:
return f"[bold green]for more information, see [/][bold blue]{url}[/]"


def make_hint(
comment: str | None = None,
caller: Function | None | Iterable[Function] | str = None,
Expand Down
70 changes: 35 additions & 35 deletions src/view/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@
from threading import Thread
from types import FrameType as Frame
from types import TracebackType as Traceback
from typing import (Any, Callable, Coroutine, Generic, Iterable, TextIO,
TypeVar, get_type_hints, overload)
from typing import (
Any,
Callable,
Coroutine,
Generic,
Iterable,
TextIO,
TypeVar,
get_type_hints,
overload,
)
from urllib.parse import urlencode

import ujson
Expand All @@ -32,13 +41,23 @@

from ._docs import markdown_docs
from ._loader import finalize, load_fs, load_patterns, load_simple
from ._logging import (Internal, Service, UvicornHijack, enter_server,
exit_server, format_warnings)
from ._logging import (
Internal,
Service,
UvicornHijack,
enter_server,
exit_server,
format_warnings,
)
from ._parsers import supply_parsers
from ._util import make_hint
from .config import Config, load_config
from .exceptions import (BadEnvironmentError, ConfigurationError, ViewError,
ViewInternalError)
from .exceptions import (
BadEnvironmentError,
ConfigurationError,
ViewError,
ViewInternalError,
)
from .logging import _LogArgs, log
from .response import HTML
from .routing import Route, RouteOrCallable, V, _NoDefault, _NoDefaultType
Expand All @@ -59,9 +78,7 @@
A = TypeVar("A")
T = TypeVar("T")

_ROUTES_WARN_MSG = (
"routes argument should only be passed when load strategy is manual"
)
_ROUTES_WARN_MSG = "routes argument should only be passed when load strategy is manual"
_ConfigSpecified = None
_CurrentFrame = None

Expand Down Expand Up @@ -189,18 +206,15 @@ async def send(obj: dict[str, Any]):
truncated_route = route[: route.find("?")] if "?" in route else route
query_str = _format_qs(query or {})
headers_list = [
(key.encode(), value.encode())
for key, value in (headers or {}).items()
(key.encode(), value.encode()) for key, value in (headers or {}).items()
]

await self.app(
{
"type": "http",
"http_version": "1.1",
"path": truncated_route,
"query_string": urlencode(query_str).encode()
if query
else b"", # noqa
"query_string": urlencode(query_str).encode() if query else b"", # noqa
"headers": headers_list,
"method": method,
"http_version": "view_test",
Expand Down Expand Up @@ -338,9 +352,7 @@ def __init__(self, status: int = 400, message: str | None = None) -> None:
message: The (optional) message to send back to the client. If none, uses the default error message (e.g. `Bad Request` for status `400`).
"""
if status not in ERROR_CODES:
raise InvalidStatusError(
"status code can only be a client or server error"
)
raise InvalidStatusError("status code can only be a client or server error")

self.status = status
self.message = message
Expand Down Expand Up @@ -415,9 +427,7 @@ def _finalize(self) -> None:
if self.loaded:
return

warnings.warn(
"load() was never called (did you forget to start the app?)"
)
warnings.warn("load() was never called (did you forget to start the app?)")
split = self.config.app.app_path.split(":", maxsplit=1)

if len(split) != 2:
Expand Down Expand Up @@ -536,9 +546,7 @@ async def index():
"""
return self._method_wrapper(path, doc, cache_rate, post)

def delete(
self, path: str, doc: str | None = None, *, cache_rate: int = -1
):
def delete(self, path: str, doc: str | None = None, *, cache_rate: int = -1):
"""Add a DELETE route.

Args:
Expand Down Expand Up @@ -613,9 +621,7 @@ async def index():
"""
return self._method_wrapper(path, doc, cache_rate, put)

def options(
self, path: str, doc: str | None = None, *, cache_rate: int = -1
):
def options(self, path: str, doc: str | None = None, *, cache_rate: int = -1):
"""Add an OPTIONS route.

Args:
Expand Down Expand Up @@ -738,9 +744,7 @@ async def template(
else:
f = frame

return await template(
name, directory, engine, f, app=self, **parameters
)
return await template(name, directory, engine, f, app=self, **parameters)

async def markdown(
self,
Expand Down Expand Up @@ -840,9 +844,7 @@ async def _spawn(self, coro: Coroutine[Any, Any, Any]):

if self.config.log.fancy:
if not self.config.log.hijack:
raise ConfigurationError(
"hijack must be enabled for fancy mode"
)
raise ConfigurationError("hijack must be enabled for fancy mode")

enter_server()

Expand Down Expand Up @@ -902,9 +904,7 @@ def _run(self, start_target: Callable[..., Any] | None = None) -> Any:
setattr(conf, k, v)

return start(
importlib.import_module("hypercorn.asyncio").serve(
self._app, conf
)
importlib.import_module("hypercorn.asyncio").serve(self._app, conf)
)
else:
raise NotImplementedError("viewserver is not implemented yet")
Expand Down
3 changes: 1 addition & 2 deletions src/view/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from typing import (Any, ClassVar, Set, TypeVar, Union, get_origin,
get_type_hints)
from typing import Any, ClassVar, Set, TypeVar, Union, get_origin, get_type_hints

from typing_extensions import Annotated, Self, dataclass_transform, get_args

Expand Down
1 change: 1 addition & 0 deletions src/view/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,6 @@ class NeedsDependencyError(ViewError):
class InvalidTemplateError(ViewError):
"""Something is wrong with a template."""


class TypeValidationError(TypeError, ViewError):
"""Could not assign the object to the target type."""
17 changes: 14 additions & 3 deletions src/view/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

from ._util import run_path
from .exceptions import DuplicateRouteError, InvalidRouteError
from .routing import (Callable, Method, Route, RouteOrCallable, delete, get,
options, patch, post, put)
from .routing import (
Callable,
Method,
Route,
RouteOrCallable,
delete,
get,
options,
patch,
post,
put,
)
from .routing import route as route_impl
from .typing import StrMethod, ViewRoute

Expand All @@ -30,6 +40,7 @@

RouteInput = Callable[[RouteOrCallable], Route]


def _get_method_enum(method: StrMethod | None | Method) -> Method:
if isinstance(method, str):
method = method.lower() # type: ignore
Expand Down Expand Up @@ -65,7 +76,7 @@ def path(
raise InvalidRouteError(f"no route in {path_or_function}")
else:
route = path_or_function

if not isinstance(route, Route):
method_enum = _get_method_enum(method)
func = _FUNC_MAPPINGS[method_enum]
Expand Down
Loading