Skip to content

Commit

Permalink
Improve the routing and testing documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertoPrevato committed Nov 19, 2023
1 parent 8ed6348 commit cf04a42
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 39 deletions.
2 changes: 1 addition & 1 deletion docs/remotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ For example:
original client IP address must also be forwarded in a header.
* the *path* of web requests can be changed while being proxied (e.g. NGINX
configured to proxy requests to `/example` to the root `/` of a web
application)
application).

This information may be important in request processing, for example in
redirects, authentication, link generation when absolute URLs are needed, and
Expand Down
189 changes: 161 additions & 28 deletions docs/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This page describes:
- [X] How to define a catch-all route.
- [X] How to define a fallback route.
- [X] How to use sub-routers and filters.
- [X] How to use the default router and other routers.

## Defining request handlers

Expand Down Expand Up @@ -51,32 +52,12 @@ The following example shows how to define a request handler for the root
path of a web application "/":

```python
from blacksheep import Application, get

app = Application(show_error_details=True)


@get("/")
def hello_world():
return "Hello World"
```

It is possible to assign router methods to variables, to reduce code verbosity:

```python
from blacksheep import Application, get, post

app = Application(show_error_details=True)
from blacksheep import get


@get("/")
def hello_world():
return "Hello World"


@post("/message")
def create_message(text: str):
return "TODO"
```

Alternatively, the application router offers a `route` method:
Expand Down Expand Up @@ -177,8 +158,7 @@ def get_cat(cat_id):
...
```

It is also possible to specify the expected type, using standard `typing`
annotations:
It is also possible to specify the expected type, using `typing` annotations:

```python

Expand Down Expand Up @@ -247,11 +227,11 @@ The following value patterns are built-in:

| Value pattern | Description |
| ------------- | --------------------------------------------------------------------------------- |
| str | Any value that doesn't contain a slash "/". |
| int | Any value that contains only numeric characters. |
| float | Any value that contains only numeric characters and eventually a dot with digits. |
| path | Any value to the end of the path. |
| uuid | Any value that matches the UUID value pattern. |
| `str` | Any value that doesn't contain a slash "/". |
| `int` | Any value that contains only numeric characters. |
| `float` | Any value that contains only numeric characters and eventually a dot with digits. |
| `path` | Any value to the end of the path. |
| `uuid` | Any value that matches the UUID value pattern. |

To define custom value patterns, extend the `Route.value_patterns` dictionary.
The key of the dictionary is the name used by the parameter, while the value is
Expand Down Expand Up @@ -371,3 +351,156 @@ class CustomFilter(RouteFilter):

example_router = Router(filters=[CustomFilter()])
```

## Using the default router and other routers

The examples in the documentation show how to register routes using methods
imported from the BlackSheep package:

```python
from blacksheep import get

@get("")
async def home():
...
```

Or, for controllers:

```python
from blacksheep.server.controllers import Controller, get


class Home(Controller):

@get("/")
async def index(self):
...
```

In this case routes are registered using default singleton routers, used if an
application is instantiated without specifying a router:

```python
from blacksheep import Application


# This application uses the default sigleton routers exposed by BlackSheep:
app = Application()
```

This works in most scenarios, when a single `Application` instance per process
is used. For more complex scenarios, it is possible to instantiate a router
and use it as desired:

```python
# app/router.py

from blacksheep import Router


router = Router()
```

And use it when registering routes:

```python
from app.router import router


@router.get("/")
async def home():
...
```

It is also possible to expose the router methods to reduce code verbosity, like
the BlackSheep package does:

```python
# app/router.py

from blacksheep import Router


router = Router()


get = router.get
post = router.post

# ...
```


```python
from app.router import get


@get("/")
async def home():
...
```

Then specify the router when instantiating the application:

```python
from blacksheep import Application

from app.router import router


# This application uses the router instantiated in app.router:
app = Application(router=router)
```

### Controllers dedicated router

Controllers need a different kind of router, an instance of
`blacksheep.server.routing.RoutesRegistry`. If using dedicated router for
controllers is desired, do instead:

```python
# app/controllers.py

from blacksheep import RoutesRegistry


controllers_router = RoutesRegistry()


get = controllers_router.get
post = controllers_router.post

# ...
```

Then when defining your controllers:

```python
from blacksheep.server.controllers import Controller

from app.controllers import get, post


class Home(Controller):

@get("/")
async def index(self):
...
```

```python
from blacksheep import Application

from app.controllers import controllers_router


# This application uses the controllers' router instantiated in app.controllers:
app = Application()
app.controllers_router = controllers_router
```

!!! info "About Router and RoutesRegistry"
Controllers routes use a "RoutesRegistry" to support dynamic generation of
paths by controller class name. Controllers routes are evaluated and merged
into `Application.router` when the application starts.
16 changes: 6 additions & 10 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,15 @@ definition of the TODOs API. Start with the following contents:

from blacksheep import get, post, delete
from domain import ToDo, CreateToDoInput
from typing import List, Optional


@get("/api/todos")
async def get_todos() -> List[ToDo]:
async def get_todos() -> list[ToDo]:
...


@get("/api/todos/{todo_id}")
async def get_todo(todo_id) -> Optional[ToDo]:
async def get_todo(todo_id) -> ToDo | None:
...


Expand Down Expand Up @@ -255,13 +254,11 @@ API to work with data stored in memory:
```python
# ./app/routes/todos.py

from typing import Dict, List, Optional

from blacksheep import get, delete, not_found, post
from domain import CreateToDoInput, ToDo


_MOCKED: Dict[int, ToDo] = {
_MOCKED: dict[int, ToDo] = {
1: ToDo(
id=1,
title="BlackSheep Documentation",
Expand All @@ -281,16 +278,16 @@ _MOCKED: Dict[int, ToDo] = {


@get("/api/todos")
async def get_todos() -> List[ToDo]:
async def get_todos() -> list[ToDo]:
return list(_MOCKED.values())


@get("/api/todos/{todo_id}")
async def get_todo(todo_id: int) -> Optional[ToDo]:
async def get_todo(todo_id: int) -> ToDo | None:
try:
return _MOCKED[todo_id]
except KeyError:
return not_found()
return not_found() # type: ignore


@post("/api/todos")
Expand All @@ -306,7 +303,6 @@ async def delete_todo(todo_id: int) -> None:
del _MOCKED[todo_id]
except KeyError:
pass

```

Now that the API is mocked, let's see how to add tests for it.
Expand Down
2 changes: 2 additions & 0 deletions docs/versions/migrating-to-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ async def add_example(self, example: str):
...
```

For more information on the above, read [_Using the default router and other routers_](/blacksheep/routing/#using-the-default-router-and-other-routers).

All modules inside `routes` and `controllers` packages are imported
automatically in v2. Automatic import works relatively to where a BlackSheep
application is instantiated. In the structure described below, the modules in
Expand Down

0 comments on commit cf04a42

Please sign in to comment.