From 6b7fa713f490e402ae3147b32d8566e90f32ab97 Mon Sep 17 00:00:00 2001 From: elliotwaite <1767836+elliotwaite@users.noreply.github.com> Date: Fri, 15 Dec 2023 15:33:05 +0800 Subject: [PATCH] Fix the docs in several places --- docs/about.md | 6 +-- docs/anti-request-forgery.md | 10 ++--- docs/application.md | 55 +++++++++++++------------- docs/authentication.md | 8 ++-- docs/authorization.md | 20 +++++----- docs/background-tasks.md | 10 ++--- docs/binders.md | 22 +++++------ docs/cli.md | 18 ++++----- docs/client.md | 20 +++++----- docs/compression.md | 2 +- docs/contributing.md | 6 +-- docs/controllers.md | 66 ++++++++++++++++---------------- docs/cors.md | 2 +- docs/dataprotection.md | 15 ++++---- docs/dependency-injection.md | 30 +++++++-------- docs/examples/marshmallow.md | 12 +++--- docs/extensions.md | 10 ++--- docs/getting-started.md | 25 ++++++------ docs/hsts.md | 10 ++--- docs/index.md | 6 +-- docs/middlewares.md | 10 ++--- docs/mounting.md | 8 ++-- docs/mvc-project-template.md | 62 +++++++++++++++--------------- docs/openapi.md | 49 ++++++++++++------------ docs/openid-connect.md | 29 +++++++------- docs/remotes.md | 6 +-- docs/request-handlers.md | 49 ++++++++++++------------ docs/requests.md | 31 +++++++-------- docs/responses.md | 60 ++++++++++++++--------------- docs/routing.md | 37 +++++++++--------- docs/settings.md | 14 +++---- docs/static-files.md | 10 ++--- docs/templating.md | 6 +-- docs/testing.md | 8 ++-- docs/versions/migrating-to-v2.md | 36 ++++++++--------- docs/websocket.md | 8 ++-- 36 files changed, 392 insertions(+), 384 deletions(-) diff --git a/docs/about.md b/docs/about.md index 24dd133..823ffdb 100644 --- a/docs/about.md +++ b/docs/about.md @@ -1,15 +1,15 @@ # About BlackSheep BlackSheep is a web framework for Python asyncio designed to facilitate the -implementation of stateless APIs and general purpose web applications. It is +implementation of stateless APIs and general-purpose web applications. It is inspired by [Flask](https://flask.palletsprojects.com/en/1.1.x/) and [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-5.0); it recreates several features from both these web frameworks. The concept of -automatic binding of request parameters by request handler's signature and +automatic binding of request parameters by the request handler's signature and dependency injection of required services (as it happens in ASP.NET Core) is what makes BlackSheep unique today, in the context of Python web frameworks. -The project, as several other web frameworks for Python, is the fruit of the +The project, like several other web frameworks for Python, is the fruit of the creative ferment around Yury Selivanov’s work, and the article [uvloop: Blazing fast Python networking](https://magic.io/blog/uvloop-blazing-fast-python-networking/) from diff --git a/docs/anti-request-forgery.md b/docs/anti-request-forgery.md index 2370767..01a9291 100644 --- a/docs/anti-request-forgery.md +++ b/docs/anti-request-forgery.md @@ -4,18 +4,18 @@ Cross-site request forgery, also known as XSRF or CSRF, is a kind of attack that exploits situations in which browsers automatically include credentials in web requests. -Example of such situations are: +Examples of such situations are: - Cookies are automatically included in web requests, so if an application uses cookie-based authentication, credentials are sent automatically - After a user signs in with Basic or Digest authentication, the browser automatically sends the credentials until the session ends -If a web application uses cookie based authentication or other features that +If a web application uses cookie-based authentication or other features that cause credentials to be automatically included in web requests, it requires anti-forgery measures. -BlackSheep implements built-in support for anti request forgery validation, this +BlackSheep implements built-in support for anti-request-forgery validation, this page describes how to use the built-in solution. !!! tip @@ -115,7 +115,7 @@ forbid iframes. the objective of the tag is to obtain an input element containing an anti-forgery token, not to achieve Cross-Site Request Forgery! -An example of rendered view looks like the following: +An example of a rendered view looks like the following: ```html @@ -138,7 +138,7 @@ Validation is applied by default to all `DELETE PATCH POST PUT` web requests. Requests using other methods are not validated as they are not supposed to change the state and should execute read-only operations. -!!! danger "Important note about tokens generation" +!!! danger "Important note about token generation" Tokens are signed using symmetric encryption. For your production environments, configure application secrets using environment variables as described in [data protection](../dataprotection/). diff --git a/docs/application.md b/docs/application.md index ad0f56a..fabd5ef 100644 --- a/docs/application.md +++ b/docs/application.md @@ -1,15 +1,15 @@ # The Application class -The `Application` class in BlackSheep is responsible of handling the -application life cicle (start, working state, stop), routing, web requests, -exceptions. This page describes details of the `Application` class: +The `Application` class in BlackSheep is responsible for handling the +application life cycle (start, working state, stop), routing, web requests, +and exceptions. This page describes the details of the `Application` class: - [X] How to handle errors. - [X] Application events and life cycle. ## Handling errors -BlackSheep catches any unhandled exception that happens during the execution of -request handlers, producing a `HTTP 500 Internal Server Error` response. To see +BlackSheep catches any unhandled exception that happen during the execution of +request handlers, producing an `HTTP 500 Internal Server Error` response. To see this in practice, start an application like the following: ```python @@ -26,7 +26,7 @@ def crash_test(): And observe how a request to its root produces a response with HTTP status 500, and the text "Internal server error". -Exception details are hidden to the client by default: it would be a security +Exception details are hidden from the client by default: it would be a security issue if the web application returned error details to the client. However, while developing and occasionally while investigating issues, it is useful to be able to obtain error details directly from the web requests that are @@ -41,7 +41,7 @@ trace, serving a page like the following: ![Internal server error page](./img/internal-server-error-page.png) -Consider using environmental variables to handle this kind of settings that +Consider using environmental variables to handle these kinds of settings that can vary across environments. For example: ```python @@ -63,11 +63,11 @@ def crash_test(): ### Configuring exceptions handlers -The BlackSheep `Application` object has a `exceptions_handlers` dictionary that -defines how errors should be handled. When an exception happens while handling -a web request and reaches the application, the application checks if there is a -matching handler for that kind of exception. An exception handler is defined as -a function with the following signature: +The BlackSheep `Application` object has an `exceptions_handlers` dictionary +that defines how errors should be handled. When an exception happens while +handling a web request and reaches the application, the application checks if +there is a matching handler for that kind of exception. An exception handler is +defined as a function with the following signature: ```python from blacksheep import Request, Response, text @@ -95,14 +95,14 @@ app.exceptions_handlers[CustomException] = exception_handler @get('/') async def home(request): - # of course, the exception can be risen at any point + # of course, the exception can be raised at any point # for example in the business logic layer raise CustomException() ``` Exceptions inheriting from `HTTPException` can be mapped to handlers by their -type or by their status code, using `int` keys; while user defined exceptions +type or by their status code, using `int` keys; while user-defined exceptions are mapped to handlers by their type. When an exception handler is registered for a type of exception, all subclasses @@ -139,7 +139,7 @@ from blacksheep.messages import Request class MyApp(Application): async def handle_internal_server_error(self, request: Request, exc: Exception): - # TODO: handle this like you wish! + # TODO: handle this as you wish! return json({"message": "Oh, no!"}, 500) ``` @@ -149,19 +149,19 @@ class MyApp(Application): A BlackSheep application exposes three events: **on_start**, **after_start**, **on_stop**. These events can be used to configure callbacks and services that -depend on application lifecycle. The application class also offers a useful +depend on the application lifecycle. The application class also offers a useful method to configure objects that need to be initialized when the application -starts, and disposed when the application stops: **lifespan**. +starts, and disposed of when the application stops: **lifespan**. ### Using the lifespan decorator The `Application.lifespan` method can be used to register objects bound to the application life cycle. Common examples of such objects are HTTP clients and database clients, since they use connection pools that can be initialized -and must be disposed when the application stops. +and must be disposed of when the application stops. The following example illustrates how to use the `@app.lifespan` decorator to -create an HTTP `ClientSession` that will be disposed when the application +create an HTTP `ClientSession` that will be disposed of when the application stops. Note how the instance of `ClientSession` is also bound to application services, so that it can be injected into request handlers that need it. @@ -183,7 +183,7 @@ async def register_http_client(): app.services.register(ClientSession, instance=client) yield - print("HTTP client disposed") + print("HTTP client disposed of") @router.get("/") @@ -203,7 +203,8 @@ if __name__ == "__main__": before the `yield` statement executes when the application starts, and what is defined after the `yield` statement executes when the application stops. -The following example illustrates how a `redis-py` [connection can be disposed](https://redis.readthedocs.io/en/stable/examples/asyncio_examples.html) +The following example illustrates how a `redis-py` [connection can be disposed +of](https://redis.readthedocs.io/en/stable/examples/asyncio_examples.html) using the same method: ```python @@ -214,7 +215,7 @@ import redis.asyncio as redis @app.lifespan async def configure_redis(): """ - Configure an async Redis client, and dispose its connections when the + Configure an async Redis client, and dispose of its connections when the application stops. See: https://redis.readthedocs.io/en/stable/examples/asyncio_examples.html @@ -239,8 +240,8 @@ async def configure_redis(): ### on_start This event should be used to configure things such as new request handlers, -and service registered in `app.services`, such as database connection pools, -HTTP client sessions. +and services registered in `app.services`, such as database connection pools, +and HTTP client sessions. ### after_start @@ -254,7 +255,7 @@ API specification file at this point. This event should be used to fire callbacks that need to happen when the application is stopped. For example, disposing of services that require disposal, such as -database connection pools, HTTP client sessions using connection pools. +database connection pools, and HTTP client sessions using connection pools. ### Application life cycle @@ -281,8 +282,8 @@ are fired, and the state of the application when they are executed. @app.on_start - async def before_start(application: Application) -> None: - print("Before start") + async def on_start(application: Application) -> None: + print("On start") @app.after_start diff --git a/docs/authentication.md b/docs/authentication.md index d28eb60..50a8e78 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -1,5 +1,5 @@ # Authentication in BlackSheep -The words _authentication strategy_ in the context of a web application refer +The words "authentication strategy" in the context of a web application refer to the ability to identify the user who is using the application. BlackSheep implements a built-in authentication strategy for request handlers. This page describes: @@ -167,7 +167,7 @@ inside **[guardpost](https://github.com/Neoteroi/guardpost)** library. ## Writing a custom authentication handler The example below shows how to configure a custom authentication handler that -obtains user's identity for each web request. +obtains the user's identity for each web request. ```python from blacksheep import Application, Request, auth, get, json @@ -269,9 +269,9 @@ Gets the output: `{"name":"Jan Kowalski"}`. _The application has been started on port 44555 (e.g. `uvicorn server:app --port=44555`)._ -## Reading user's context +## Reading a user's context -The example below shows how user's identity can be read from the web request: +The example below shows how a user's identity can be read from the web request: === "Using binders (recommended)" diff --git a/docs/authorization.md b/docs/authorization.md index 427978d..5af1314 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -91,11 +91,11 @@ or more requirements to be satisfied in order for authorization to succeed. The next example explains how to configure an authorization policy that checks for user's roles from claims. -## Defining an authorization policy that checks user's claims +## Defining an authorization policy that checks a user's claims The example below shows how to configure an authorization handler that -validates user's claims (looking for a "role" claim that might be coming from a -JWT). +validates a user's claims (looking for a "role" claim that might be coming from +a JWT). ```python from blacksheep.server.authorization import Policy @@ -200,9 +200,9 @@ async def only_for_administrators(): ## Using the default policy The method `app.use_authorization()`, when used without arguments, returns an -instance of `AuthorizationStrategy` from `guardpost` library. This object can -be configured to use a default policy, for example to require an authenticated -user by default for all request handlers. +instance of `AuthorizationStrategy` from the `guardpost` library. This object +can be configured to use a default policy, for example to require an +authenticated user by default for all request handlers. ```python authorization = app.use_authorization() @@ -232,10 +232,10 @@ async def for_anybody(user: Optional[User]): ## Specifying authentication schemes for request handlers In some scenarios it is necessary to specify multiple authentication schemes -for web applications: for example the same application might handle authentication -obtained through `GitHub` OAuth app and `Azure Active Directory (AAD)`. -In such scenarios, it might be necessary to restrict access to some endpoints -by authentication method, too. +for web applications: for example, the same application might handle +authentication obtained through the `GitHub` OAuth app and `Azure Active +Directory (AAD)`. In such scenarios, it might be necessary to restrict access +to some endpoints by authentication method, too. To do so: diff --git a/docs/background-tasks.md b/docs/background-tasks.md index 7192a30..61fbda2 100644 --- a/docs/background-tasks.md +++ b/docs/background-tasks.md @@ -3,10 +3,10 @@ This page describes how to start background tasks in request handlers, and how to configure background tasks that run periodically during the application's lifetime. -## How to handle a request in background +## How to handle a request in the background -The following example shows how to handle a web request in background, which -is the use case for the [HTTP 202 Accepted](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202) +The following example shows how to handle a web request in the background, +which is the use case for the [HTTP 202 Accepted](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202) response status code. ```python @@ -35,7 +35,7 @@ def home() -> Response: ## How to configure background tasks -The following example shows how to configure a background tasks, including +The following example shows how to configure a background task, including the activation of a service resolved by the DI container, running periodically once every second: @@ -64,7 +64,7 @@ class Foo: async def task_example(app: Application) -> None: # example background task, running once every second, - # this example also shows how to activate a service using the CI container + # this example also shows how to activate a service using the DI container while True: print(get_current_timestamp()) diff --git a/docs/binders.md b/docs/binders.md index acf45d5..b256675 100644 --- a/docs/binders.md +++ b/docs/binders.md @@ -2,7 +2,7 @@ BlackSheep implements automatic binding of parameters for request handlers, a feature inspired by "Model Binding" in the [ASP.NET web framework](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.2). -This feature improves code quality and developer's experience, since it +This feature improves code quality and the developer experience since it provides a strategy to read values from request objects in a consistent way and removes the need to write parts that read values from the request object inside request handlers. It also enables a more accurate generation of [OpenAPI @@ -67,7 +67,7 @@ are not annotated with types, or are **not** annotated with types that inherit from `BoundValue` class, defined in `blacksheep.server.bindings`. !!! warning - A parameter with name "request" is always bound to the instance of + A parameter with the name "request" is always bound to the instance of the `Request` of the web request. ### Explicit binding @@ -98,11 +98,11 @@ async def create_cat( In the example above, `create_cat_handler` is obtained from `application.services`, an exception is thrown if the the service cannot be resolved. This happens if the service is not registered in application -services, or any of services on which it depends is not registered +services, or any of the services on which it depends is not registered (see [_Service resolution_](../dependency-injection/#service-resolution) for more information on services that depend on other services). -`input` is obtained reading the request payload, parsing it as JSON, and +`input` is obtained by reading the request payload, parsing it as JSON, and creating an instance of CreateCatInput from it. If an exception occurs while trying to parse the request payload or when instantiating the `CreateCatInput`, the framework produces automatically a `400 Bad Request` response for the client. @@ -112,7 +112,7 @@ is instantiated using `cls(**data)`. If it necessary to parse dates or other complex types that are not handled by JSON deserialization, this must be done in the constructor of the class. To handle gracefully a JSON payload having extra unused properties, use `*args` in your class constructor: `__init__(one, -two, three, *args)__`. +two, three, *args)`. ## Optional parameters Optional parameters can be defined in one of these ways: @@ -128,8 +128,8 @@ async def example( page: int = 1, search: str = "", ): - # page is read from query string, if specified, otherwise defaults to 1 - # search is read from query string, if specified, otherwise defaults to "" + # page is read from the query string, if specified, otherwise defaults to 1 + # search is read from the query string, if specified, otherwise defaults to "" ... ``` @@ -142,8 +142,8 @@ async def example( page: Optional[int], search: Optional[str], ): - # page is read from query string, if specified, otherwise defaults to None - # search is read from query string, if specified, otherwise defaults to None + # page is read from the query string, if specified, otherwise defaults to None + # search is read from the query string, if specified, otherwise defaults to None ... ``` @@ -206,8 +206,8 @@ async def example( | FromText | Request payload read as text, using UTF-8 encoding. | | FromBytes | Request payload read as raw bytes. | | FromFiles | Request payload of file type. | -| ClientInfo | Client ip and port information obtained from the request ASGI scope, as Tuple[str, int]. | -| ServerInfo | Server ip and port information obtained from the request scope. | +| ClientInfo | Client IP and port information obtained from the request ASGI scope, as Tuple[str, int]. | +| ServerInfo | Server IP and port information obtained from the request scope. | | RequestUser | Request's identity. | | RequestURL | Request's URL. | | RequestMethod | Request's HTTP method. | diff --git a/docs/cli.md b/docs/cli.md index a225dbc..814ebeb 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -4,7 +4,7 @@ The second version of the web framework offers a command-line interface (CLI) to bootstrap new projects using templates, inspired by similar CLIs of popular front-end web frameworks. -This page describes the CLI in details, covering the following subjects: +This page describes the CLI in detail, covering the following subjects: - [X] How to install the `blacksheep-cli`. - [X] How to use its help. @@ -37,7 +37,7 @@ with the list of command groups: ![CLI help](./img/cli-help.png) -To display the help of a specific commands group, use the command group name +To display the help of a specific command group, use the command group name followed by `--help`, like in the following example: ![blacksheep templates --help](./img/cli-group-help.png) @@ -68,8 +68,8 @@ in the user's folder, use the `blacksheep templates details` command: Use the `blacksheep create` command to bootstrap a project using one of the supported templates. The command will prompt for the necessary -input to bootstrap a new project. Aside from project name and template type, -each project template defines the parameters that are needed. +input to bootstrap a new project. Aside from the project name and template +type, each project template defines the parameters that are needed. ![blacksheep create command](./img/cli-create-demo.gif) @@ -117,12 +117,12 @@ The `blacksheep-cli` uses [`Cookiecutter`](https://cookiecutter.readthedocs.io/e under the hood, with [`questionary`](https://pypi.org/project/questionary/) to offer better prompts. -The official templates are `cookiecutter` templates, that can be used in non-interactive -way if desired, using the `cookiecutter` CLI. This can be useful to bootstrap new -projects in automated jobs (CI/CD). +The official templates are `cookiecutter` templates, that can be used in a +non-interactive way if desired, using the `cookiecutter` CLI. This can be +useful to bootstrap new projects in automated jobs (CI/CD). -For example, to bootstrap a new project using the API template, with name "foo" -and using TOML files for application settings: +For example, to bootstrap a new project using the API template, with the name +"foo" and using TOML files for application settings: ```bash cookiecutter https://github.com/Neoteroi/BlackSheep-API --no-input project_name=foo app_settings_format=TOML diff --git a/docs/client.md b/docs/client.md index 77de77b..39f5eba 100644 --- a/docs/client.md +++ b/docs/client.md @@ -5,7 +5,7 @@ BlackSheep includes an implementation of HTTP Client for HTTP 1.1. ## Client features - HTTP connection pooling -- User friendly handling of SSL contexts (safe by default) +- User-friendly handling of SSL contexts (safe by default) - Support for client side middlewares - Automatic handling of redirects (can be disabled, validates circular redirects and maximum number of redirects - redirects to URN are simply @@ -38,7 +38,7 @@ loop.run_until_complete(client_example(loop)) The HTTP client in BlackSheep implements connection pooling. Meaning that connections to the same host and port are kept in memory and reused for different request-response cycles, when possible. By default, connections are -not disposed as long as they are kept open. +not disposed of as long as they are kept open. Implementation: [/blacksheep/client/pool.py](https://github.com/RobertoPrevato/BlackSheep/blob/master/blacksheep/client/pool.py). @@ -51,7 +51,7 @@ The HTTP Client supports middlewares. Middlewares on the server are functions that are executed in order, at every request-response cycle and enable manipulation of incoming requests and outgoing responses. Middlewares support interruption of the chain: that is, returning an HTTP response without firing -all handlers in the chain, for example to return HTTP 401 Unauthorized when +all handlers in the chain, for example, to return HTTP 401 Unauthorized when applying an authentication strategy. The HTTP client can benefit from the same design pattern, and this is supported in BlackSheep. @@ -63,7 +63,7 @@ async def client_example_middleware(request, next_handler): # do something before the request is sent response = await next_handler(request) - # do something with the response from remote server + # do something with the response from the remote server return response client = ClientSession() @@ -74,7 +74,7 @@ client.configure() ## Considerations about the ClientSession class The `ClientSession` owns by default a connections pool, if none is specified for -it. The connections pool is automatically disposed when the client is exited, +it. The connections pool is automatically disposed of when the client is exited, if it was created for the client. !!! danger "Connection pooling is important" @@ -84,7 +84,7 @@ if it was created for the client. negative effects on the performance of the application. It is recommended to instantiate a single instance of HTTP client and -register it as service of the application, using the `@app.lifespan` method: +register it as a service of the application, using the `@app.lifespan` method: ```python ```python @@ -101,7 +101,7 @@ async def register_http_client(): app.services.register(ClientSession, instance=client) yield - print("HTTP client disposed") + print("HTTP client disposed of") @router.get("/") @@ -110,6 +110,6 @@ async def home(http_client: ClientSession): return {"ok": True, "client_instance_id": id(http_client)} ``` -When following this approach, the http client can be automatically injected to -request handlers, and services that need it, and is automatically disposed when -the application is stopped. +When following this approach, the HTTP client can be automatically injected +into request handlers, and services that need it, and is automatically disposed +of when the application is stopped. diff --git a/docs/compression.md b/docs/compression.md index 5748720..4a62baf 100644 --- a/docs/compression.md +++ b/docs/compression.md @@ -36,7 +36,7 @@ use_gzip_compression(app) !!! warning "Not for streamed content" The `GzipMiddleware` does not compress bytes streamed using the `StreamedContent` class (used by default when serving files), it only - compress whole bodies like, for example, those that are generated when + compresses whole bodies like, for example, those that are generated when returning `JSON` content to the client. ### Options diff --git a/docs/contributing.md b/docs/contributing.md index 025d45c..7a4305a 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -107,12 +107,12 @@ pytest --cov-report html --cov=blacksheep It doesn't matter, as long as branch names don't contain anything that violates the _Code of Conduct_ included in the project's repository. As a general rule -of thumb, branch names should have a descriptive name, or refer the number of -an issue in their name (e.g. `fix_102`). +of thumb, branch names should have a descriptive name, or refer to the number +of an issue in their name (e.g. `fix_102`). ## setup.py -It is intention of `blacksheep`'s author to always keep `setup.py` files as +It is the intention of `blacksheep`'s author to always keep `setup.py` files as stupid and simple as possible. So please don't modify the `setup.py` file to be "smarter" or more dynamic without prior discussion in an issue. diff --git a/docs/controllers.md b/docs/controllers.md index b078153..611c162 100644 --- a/docs/controllers.md +++ b/docs/controllers.md @@ -2,7 +2,7 @@ BlackSheep has built-in features to support MVC (Model, View, Controller) architecture. A `Controller` is a class having at least one method registered -as request handler (i.e. associated to a route). A Controller is instantiated +as a request handler (i.e. associated to a route). A Controller is instantiated at each web request, when a web request is matched by a route defined in that type of controller. @@ -16,39 +16,39 @@ reading this page. !!! tip "For Flask users..." If you come from Flask, controllers in BlackSheep can be considered - equivalent of Flask's Blueprints, as they allow to group request handlers + equivalent to Flask's Blueprints, as they allow to group request handlers in dedicated modules and classes. ## The Controller class Controllers implement several methods to simplify returning responses. These -are the same described at [Responses](../responses/), but they can be overridden +are the same described in [Responses](../responses/), but they can be overridden in subclasses of `Controller` and they remove the need to import functions. -| Method | Description | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **ok** | Returns an HTTP 200 OK response, with optional message; sent as plain text or JSON. | -| **status_code** | Returns a plain response with given status, with optional message; sent as plain text or JSON. | -| **created** | Returns an HTTP 201 Created response, to the given location and with optional JSON content. | -| **accepted** | Returns an HTTP 202 Accepted response, with optional message; sent as plain text or JSON. | -| **no_content** | Returns an HTTP 204 No Content response. | -| **json** | Returns a response with application/json content, and given status (default HTTP 200 OK). | -| **pretty_json** | Returns a response with indented application/json content, and given status (default HTTP 200 OK). | -| **text** | Returns a response with text/plain content, and given status (default HTTP 200 OK). | -| **html** | Returns a response with text/html content, and given status (default HTTP 200 OK). | -| **moved_permanently** | Returns an HTTP 301 Moved Permanently response, to the given location. | -| **redirect** | Returns an HTTP 302 Found response (commonly called redirect), to the given location. | -| **see_other** | Returns an HTTP 303 See Other response, to the given location. | -| **not_modified** | Returns an HTTP 304 Not Modified response. | -| **temporary_redirect** | Returns an HTTP 307 Temporary Redirect response, to the given location. | -| **permanent_redirect** | Returns an HTTP 308 Permanent Redirect response, to the given location. | -| **bad_request** | Returns an HTTP 400 Bad Request response, with optional message; sent as plain text or JSON. | -| **unauthorized** | Returns an HTTP 401 Unauthorized response, with optional message; sent as plain text or JSON. | -| **forbidden** | Returns an HTTP 403 Forbidden response, with optional message; sent as plain text or JSON. | -| **not_found** | Returns an HTTP 404 Not Found response, with optional message; sent as plain text or JSON. | -| **view** | Returns a view rendered synchronously. | -| **view_async** | Returns a view rendered asynchronously. | -| **file** | Returns a binary file response with given content type and optional file name, for download (attachment) (default HTTP 200 OK). This method supports both call with bytes, or a generator yielding chunks. | +| Method | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **ok** | Returns an HTTP 200 OK response, with an optional message; sent as plain text or JSON. | +| **status_code** | Returns a plain response with the given status, and with an optional message; sent as plain text or JSON. | +| **created** | Returns an HTTP 201 Created response, to the given location and with optional JSON content. | +| **accepted** | Returns an HTTP 202 Accepted response, with an optional message; sent as plain text or JSON. | +| **no_content** | Returns an HTTP 204 No Content response. | +| **json** | Returns a response with application/json content, and the given status (default HTTP 200 OK). | +| **pretty_json** | Returns a response with indented application/json content, and the given status (default HTTP 200 OK). | +| **text** | Returns a response with text/plain content, and the given status (default HTTP 200 OK). | +| **html** | Returns a response with text/html content, and the given status (default HTTP 200 OK). | +| **moved_permanently** | Returns an HTTP 301 Moved Permanently response, to the given location. | +| **redirect** | Returns an HTTP 302 Found response (commonly called redirect), to the given location. | +| **see_other** | Returns an HTTP 303 See Other response, to the given location. | +| **not_modified** | Returns an HTTP 304 Not Modified response. | +| **temporary_redirect** | Returns an HTTP 307 Temporary Redirect response, to the given location. | +| **permanent_redirect** | Returns an HTTP 308 Permanent Redirect response, to the given location. | +| **bad_request** | Returns an HTTP 400 Bad Request response, with an optional message; sent as plain text or JSON. | +| **unauthorized** | Returns an HTTP 401 Unauthorized response, with an optional message; sent as plain text or JSON. | +| **forbidden** | Returns an HTTP 403 Forbidden response, with an optional message; sent as plain text or JSON. | +| **not_found** | Returns an HTTP 404 Not Found response, with an optional message; sent as plain text or JSON. | +| **view** | Returns a view rendered synchronously. | +| **view_async** | Returns a view rendered asynchronously. | +| **file** | Returns a binary file response with the given content type and optional file name, for download (attachment) (default HTTP 200 OK). This method supports being called with bytes, or a generator yielding chunks. | For information on how to use these methods, refer to the type annotations provided in the code. @@ -84,16 +84,16 @@ benefits: injection](../dependency-injection) for every single request handler * Controllers support defining an `on_request(request: Request)` method, that gets called at every web request, `on_response(response: Response)` method, - and base `route` (defined as class method) for all handlers defined in the + and a base `route` (defined as class method) for all handlers defined in the class. * Controllers provide methods to produce responses, that can be overridden in base classes to personalize the behavior of the application without monkey-patching functions -Therefore they can help avoiding code repetition in some circumstances. +Therefore they can help avoid code repetition in some circumstances. -The following example shows how dependency injection can be used for -controllers' constructors, and an implementation of `on_request` method: +The following example shows how dependency injection can be used in +controller constructors, and an implementation of the `on_request` method: ```python @@ -146,8 +146,8 @@ get = app.controllers_router.get ## The APIController class The `APIController` class is a kind of `Controller` dedicated to API -definitions. An APIController offers some properties to enable versioning -of routes and adding a common path prefix to all routes, for example prepending +definitions. An APIController offers some properties to enable versioning of +routes and adding a common path prefix to all routes, for example, prepending "/v1/" fragment to all routes and the name of the controller class. ```python diff --git a/docs/cors.md b/docs/cors.md index 0d7b0a6..6cf10dc 100644 --- a/docs/cors.md +++ b/docs/cors.md @@ -42,7 +42,7 @@ app.use_cors( | expose_headers | Controls the value of [Access-Control-Expose-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers). 🗡️ | | max_age | Controls the value of [Access-Control-Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age), defaults to 5 seconds. | -🗡️ the value can be a string of values separated by space, comma, or semi-colon, or a list. +🗡️ The value can be a string of values separated by space, comma, or semi-colon, or a list. ## Enabling CORS for specific endpoints The example below shows how to enable CORS only for certain endpoints: diff --git a/docs/dataprotection.md b/docs/dataprotection.md index ae61d6f..7a76a9b 100644 --- a/docs/dataprotection.md +++ b/docs/dataprotection.md @@ -1,7 +1,7 @@ # Data protection Web applications often need to protect data, so that it can be stored in -cookies or other storages. BlackSheep uses [`itsdangerous`](https://pypi.org/project/itsdangerous/) to sign and encrypt +cookies or other types of storage. BlackSheep uses [`itsdangerous`](https://pypi.org/project/itsdangerous/) to sign and encrypt information, for example when storing `claims` obtained from `id_token`s when using an integration with an identity provider using [OpenID Connect](../authentication/#oidc), or when handling [session cookies](../sessions/). @@ -9,21 +9,22 @@ Connect](../authentication/#oidc), or when handling [session cookies](../session This page documents: - [X] How to handle secrets -- [X] Examples use of data protection +- [X] Example use of data protection ## How to handle secrets Symmetric encryption is used to sign and encrypt information in several scenarios. This means that BlackSheep applications _need_ secrets to protect sensitive data in some circumstances. When keys are not specified, they are -generated automatically in memory when the application starts, for best user's -experience. +generated automatically in memory when the application starts, for the best +user experience. !!! danger This means that keys are not persisted when applications - restart, and not consistent when multiple instances of the same application - are deployed across regions, or within a same server. This is acceptable during - local development, but should not be the case in production environments. + restart, and are not consistent when multiple instances of the same + application are deployed across regions, or within the same server. This is + acceptable during local development, but should not be the case in + production environments. To use consistent keys, configure one or more environment variables like the following: diff --git a/docs/dependency-injection.md b/docs/dependency-injection.md index aef172d..4a2dd84 100644 --- a/docs/dependency-injection.md +++ b/docs/dependency-injection.md @@ -15,8 +15,8 @@ This page describes: The `Application` object exposes a `services` property that can be used to configure services. When the function signature of a request handler references -a type that is registered as service, an instance of that type is automatically -injected when the request handler is called. +a type that is registered as a service, an instance of that type is +automatically injected when the request handler is called. Consider this example: @@ -69,13 +69,13 @@ def home(foo: Foo): # <-- foo is referenced in type annotation ``` -An instance of `Foo` is injected automatically at every web request to "/". +An instance of `Foo` is injected automatically for every web request to "/". Dependency injection is implemented in a dedicated library from the same author: [`rodi`](https://github.com/RobertoPrevato/rodi). `rodi` implements dependency -injection in an unobstrusive way: it works by inspecting `__init__` methods and -doesn't require to alter the source code of classes registered as services. -`rodi` can also resolve dependencies inspecting class annotations, if an +injection in an unobtrusive way: it works by inspecting `__init__` methods and +doesn't require altering the source code of classes registered as services. +`rodi` can also resolve dependencies by inspecting class annotations, if an `__init__` method is not specified for the class to activate. ## Service resolution @@ -130,8 +130,8 @@ Produces a response like the following at "/": ## Using class annotations -In alternative to defining `__init__` methods, it is also possible to use -class annotations, like in the example below: +An alternative to defining `__init__` methods is to use class annotations, like +in the example below: ```python class A: @@ -142,7 +142,7 @@ class Foo: a: A ``` -## Understanding services' lifetime +## Understanding service lifetimes `rodi` supports services having one of these lifetimes: @@ -240,9 +240,9 @@ Produces responses like the following at "/": A2: 139976289979936 - B1: 139976289979984 + B1: 139976289979988 - B2: 139976289979984 + B2: 139976289979988 C1: 139976289978736 @@ -259,7 +259,7 @@ Note how: `rodi` provides several ways to define and instantiate services. -1. registering an exact instance as singleton +1. registering an exact instance as a singleton 2. registering a concrete class by its type 3. registering an abstract class and one of its concrete implementations 4. registering a service using a factory function @@ -368,7 +368,7 @@ app.services.add_transient_by_factory(something_factory) ``` #### Example: implement a request context -A good example of scoped service is one used to assign each web request with +A good example of a scoped service is one used to assign each web request with a trace id that can be used to identify requests for logging purposes. ```python @@ -436,10 +436,10 @@ async def home(service: Example): ``` -Services configured this way are automatically injected in request handlers, +Services configured this way are automatically injected in request handlers when a parameter name or type annotation matches a key inside `app.services`. -Services that require disposing should be disposed in `on_stop` callback: +Services that require disposing of should be disposed of in `on_stop` callback: ```python async def dispose_example(app: Application): diff --git a/docs/examples/marshmallow.md b/docs/examples/marshmallow.md index e394df6..e1c3648 100644 --- a/docs/examples/marshmallow.md +++ b/docs/examples/marshmallow.md @@ -5,12 +5,12 @@ simplicity, the example shows a case in which an array of items is validated (the marshmallow scheme is validated using `(many=True)`). A similar approach can be used with [`msgspec`](https://jcristharif.com/msgspec/) -Implementing a generic solution to validate input and produce user friendly +Implementing a generic solution to validate input and produce user-friendly error messages is not in the scope of BlackSheep, but the framework offers ways to integrate with other libraries. -This is possible defining a couple of custom binders, a custom exception, and a -custom exception handler like in the example below. +This is possible by defining a couple of custom binders, a custom exception, +and a custom exception handler like in the example below. ```python from typing import Any, TypeVar @@ -27,7 +27,7 @@ SchemaType = TypeVar("SchemaType", bound=Schema) class InvalidBodyError(Exception): """ - Kind of BadRequest exception that include error details as complex objects. + Kind of BadRequest exception that includes error details as complex objects. """ def __init__(self, data: Any): @@ -41,7 +41,7 @@ class BandMemberSchema(Schema): email = fields.Email() -# Example binding for a Marshmallow schema, to be used to obtain list of objects +# Example binding for a Marshmallow schema, to be used to obtain a list of objects class FromMultiSchema(BoundValue[SchemaType]): """ Custom bound value that can be used to describe a list of objects validated using a @@ -51,7 +51,7 @@ class FromMultiSchema(BoundValue[SchemaType]): class MultiSchemaBinder(Binder): """ - Binder that handles a FromMultiSchema, returning list of objects from a + Binder that handles a FromMultiSchema, returning a list of objects from a Marshmallow schema. """ diff --git a/docs/extensions.md b/docs/extensions.md index 4aa416c..4a788c6 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -6,9 +6,9 @@ providing integration with BlackSheep. ## Torino Torino is an advanced project built using BlackSheep for its API part, including a single page application built using React, TypeScript, HTML5, and -SASS. It consists in a private files storage and photo gallery for Azure -Storage Account. The project provides examples of how to integrate a BlackSheep -API to PostgreSQL or SQLite using SQLAlchemy, with migrations, and also how to +SASS. It consists of a private file storage and photo gallery for Azure Storage +Account. The project provides examples of how to integrate a BlackSheep API to +PostgreSQL or SQLite using SQLAlchemy, with migrations, and also how to structure a project using dependency injection, integrate with Azure Application Insights, and more. @@ -27,8 +27,8 @@ provides integration to collect telemetries about web requests. [🏠 Homepage](https://github.com/Cdayz/blacksheep-prometheus) ## Piccolo-ORM -Piccolo is a fast, user friendly ORM and query builder which supports asyncio. -Piccolo provides a CLI that lets scaffold new ASGI application, including +Piccolo is a fast, user-friendly ORM and query builder which supports asyncio. +Piccolo provides a CLI that lets you scaffold new ASGI applications, including support for BlackSheep. [🏠 Homepage](https://github.com/piccolo-orm/piccolo) diff --git a/docs/getting-started.md b/docs/getting-started.md index dc7b590..d18ee2e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -89,7 +89,7 @@ text answer from the web application: The current code configures a request handler for [HTTP GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) method at the root path of the application: `"/"`. Note how a function decorator -is used to register the `home` function as request handler: +is used to register the `home` function as a request handler: ```python @get("/") @@ -165,7 +165,7 @@ def greetings(name): return f"Hello, {name}!" ``` -Route parameters and function parameter are bound by matching name. +Route parameters and function parameters are bound by matching names. Add the fragment of code above to `server.py` and try navigating to `http://127.0.0.1:44777/World`. @@ -184,7 +184,7 @@ def mix(movie_id, actor_id): ``` Route parameters are by default treated as strings, but BlackSheep supports -automatic parsing of values, when function arguments are annotated using +automatic parsing of values when function arguments are annotated using built-in [`typing` annotations](https://docs.python.org/3/library/typing.html). For example, to define a route that handles integer route parameters and returns `HTTP 400 Bad Request` for invalid values, it is sufficient to decorate @@ -222,9 +222,9 @@ Several built-in types are handled automatically, like `str`, `bool`, `int`, ### Handling query string parameters -In the same way route parameters are injected automatically into request +In the same way, route parameters are injected automatically into request handlers by route parameters with matching names, `blacksheep` can handle -query string parameters automatically. Adds this new fragment to your +query string parameters automatically. Add this new fragment to your application: ```python @@ -251,8 +251,9 @@ def greetings_many(name: list[str]): ``` Every handler can have many input parameters from different sources: request -headers, cookies, query, route, request body, configured application services. -These are treated in more details in the dedicated page about [Binders](./binders). +headers, cookies, query, route, request body, and configured application +services. These are treated in more detail in the dedicated page about +[Binders](./binders). ### Accessing the request object @@ -273,7 +274,7 @@ def request_object(request: Request): You can name the request parameter any way you like (e.g. `request`, `req`, `foo`, etc.), as long as you keep the correct type annotation (`blacksheep.Request`). -This subject will be treated in more details in a different section. +This subject will be treated in more detail in a different section. ### Handling responses @@ -368,7 +369,7 @@ def get_cats(): return response ``` -User defined request handlers can also return arbitrary objects, which will +User-defined request handlers can also return arbitrary objects, which will be automatically converted to JSON responses. The example above could also be written this way: @@ -410,10 +411,10 @@ This tutorial covered the ABCs of creating a BlackSheep application. The general concepts presented here apply to any kind of web framework: - server side routing -- handling of query strings and route parameters -- handling of requests and responses +- handling query strings and route parameters +- handling requests and responses -The next page will describe a more articulated scenario, including handling of +The next page will describe a more articulated scenario, including handling HTML views on the server side, serving static files, and more. - [Getting started with the MVC project template](../mvc-project-template/) diff --git a/docs/hsts.md b/docs/hsts.md index a14e9fa..5946e02 100644 --- a/docs/hsts.md +++ b/docs/hsts.md @@ -3,7 +3,7 @@ is a standard feature used to instruct clients that a site should only be accessed using HTTPS, and any attempt to access it using HTTP should be converted automatically to HTTPS. -BlackSheep offers a middleware to configure HTTP Strict-Transport-Security +BlackSheep offers a middleware to configure the HTTP Strict-Transport-Security response header globally. This page explains how to use the built-in middleware to enforce HSTS on a web application. @@ -22,10 +22,10 @@ if not is_development(): ``` !!! tip "Considerations for local development" - It is generally undesirable enabling `HSTS` during local development, since - browsers get instructed to require `HTTPS` for all traffic on `localhost`. - This is why the example above configures the middleware only if the - application is not running for development. + It is generally undesirable to enable `HSTS` during local development, + since browsers get instructed to require `HTTPS` for all traffic on + `localhost`. This is why the example above configures the middleware only + if the application is not running in development mode. See [_Defining application environment_](/blacksheep/settings/#defining-application-environment) for more information. diff --git a/docs/index.md b/docs/index.md index fabba1d..a092307 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ no_comments: true --- -# BlackSheep is an asynchronous web framework to build event based web applications with Python. +# BlackSheep is an asynchronous web framework to build event-based web applications with Python.

@@ -21,7 +21,7 @@ pip install blacksheep experience thanks to hints when coding with IDEs - Built-in generation of OpenAPI Documentation, supporting version 3, YAML, and JSON -- A cross platform framework, using the most modern versions of Python +- A cross-platform framework, using the most modern versions of Python - Good performance ## Getting started @@ -34,7 +34,7 @@ To get started with BlackSheep, read these tutorials: ## Versions The documentation here refers to the current version of the web framework. For -the documentation of first version of the framework, use the links below: +the documentation of the first version of the framework, use the links below: ::cards:: cols=2 diff --git a/docs/middlewares.md b/docs/middlewares.md index 437796a..dd9a272 100644 --- a/docs/middlewares.md +++ b/docs/middlewares.md @@ -1,7 +1,7 @@ # Middlewares A BlackSheep application supports middlewares, which provide a flexible way to -define a chain of functions that handles every web requests. +define a chain of functions that handle every web request. This page covers: @@ -17,8 +17,8 @@ request in a specific order. When a function should be called only for certain routes, use instead a [decorator function](../middlewares/#wrapping-request-handlers). -Middlewares are called in order: each receives the `Request` object as first -parameter, and the next handler to be called as second parameter. Any +Middlewares are called in order: each receives the `Request` object as the +first parameter, and the next handler to be called as the second parameter. Any middleware can decide to not call the next handler, and return a `Response` object instead. For example, a middleware can be used to return an `HTTP 401 Unauthorized` response in certain scenarios. @@ -54,7 +54,7 @@ def home(): ``` -In this example, the following data would be printed to console: +In this example, the following data would be printed to the console: ``` middleware one: A middleware two: C @@ -143,7 +143,7 @@ async def home(): return "OK" ``` -**The order of decorators matters**: user defined decorators must be applied +**The order of decorators matters**: user-defined decorators must be applied before the route decorator (before `@get` in the example above). ### Define a wrapper compatible with synchronous and asynchronous functions diff --git a/docs/mounting.md b/docs/mounting.md index 1999bc5..fe4d511 100644 --- a/docs/mounting.md +++ b/docs/mounting.md @@ -134,13 +134,13 @@ starts, and actions that must happen when the application stops. ASGI web frameworks handle lifecycle events when they get dedicated messages from the underlying ASGI server (`lifespan` messages), notifying the ASGI server when initialization logic has completed. However, when an application is -mounted into another, it is not responsible of handling `lifespan` messages. +mounted into another, it is not responsible for handling `lifespan` messages. When mounted apps define initialization and shutdown logic, the application that mounts them must register their initialization and shutdown functions as part of its own events. -BlackSheep applications must always be started to work properly. To enable +BlackSheep applications must always be started to work properly. To enable the automatic handling of application events for mounted applications, use of the following options: @@ -169,7 +169,7 @@ the ASGI HTTP Server, the mounted app is also notified properly of those events. !!! info - The way the mounted app must be started and stopped depend on the + The way the mounted app must be started and stopped depends on the web framework used to implement it. The example above is correct when `child` is an instance of BlackSheep Application. @@ -312,5 +312,5 @@ at _BlackSheep-Examples_](https://github.com/Neoteroi/BlackSheep-Examples/tree/main/piccolo-admin). In this example, [Piccolo Admin](https://github.com/piccolo-orm/piccolo_admin) -is configured as mounted app under "/admin" route, providing a UI to handle +is configured as a mounted app under "/admin" route, providing a UI to handle data stored in a `SQLite` database. diff --git a/docs/mvc-project-template.md b/docs/mvc-project-template.md index 85e60cd..cf7736f 100644 --- a/docs/mvc-project-template.md +++ b/docs/mvc-project-template.md @@ -24,7 +24,7 @@ reading this one. ## Introduction to the BlackSheep CLI -The previous tutorial described the basics to create an application from +The previous tutorial described the basics of creating an application from scratch. While that knowledge is important, it is usually not desirable to start every project from scratch. BlackSheep offers a command-line interface (CLI) that can be used to start new projects. The CLI can be installed from the @@ -63,8 +63,8 @@ tutorial, answer: ``` !!! tip "blacksheep create" - It is possible to use the `create` command specifying directly project name - and template, like in: + It is possible to use the `create` command specifying the project name + and template directly, like in: - `blacksheep create some_name` - `blacksheep create some_name --template api` @@ -83,9 +83,9 @@ After a project is created, the CLI displays a message with instructions. python dev.py ``` -Install the project dependencies +Install the project dependencies: -- cd into the project folder +- `cd` into the project folder - create a new [Python virtual environment](https://docs.python.org/3/library/venv.html) (recommended but optional) - install its dependencies with `pip install -r requirements.txt` @@ -200,11 +200,11 @@ will display the response from the `Greetings.index` method. When the path of a web request matches a route defined in a controller type, a new instance of that `Controller` is created. In other words, every instance of controller is scoped to a specific web request. Just like function handlers, -controllers support automatic injection of parameters into request handlers, and -also dependency injection into their constructors (`__init__` methods). This is -a feature that improves development speed and enables cleaner code (compare -this approach with a scenario where all dependencies needs to be imported and -referenced inside function bodies by hand). +controllers support the automatic injection of parameters into request +handlers, and also dependency injection into their constructors (`__init__` +methods). This is a feature that improves development speed and enables cleaner +code (compare this approach with a scenario where all dependencies need to be +imported and referenced inside function bodies by hand). The `Controller` class implements methods to return values and offers `on_request` and `on_response` extensibility points. @@ -212,7 +212,7 @@ The `Controller` class implements methods to return values and offers !!! tip "Controllers and routes automatic import" Python modules defined inside `controllers` and `routes` packages are automatically imported by a BlackSheep application. The automatic import - happens relatively to the namespace where the application is instantiated. + happens relative to the namespace where the application is instantiated. ## Server side templating (views and models) @@ -282,8 +282,8 @@ full page, modify `hello.jinja` to use the application layout: Refresh the page at [http://localhost:44777/hello-view](http://localhost:44777/hello-view) to see the result. In this case, a page layout is applied using: `{%- extends "layout.jinja" -%}`, -with several blocks going in various area of `layout.jinja`. For more information -on layouts and features of the templating library, refer to +with several blocks going in various areas of `layout.jinja`. For more +information on layouts and features of the templating library, refer to the [Jinja2 documentation](https://jinja2docs.readthedocs.io/en/stable/). --- @@ -353,10 +353,10 @@ Models can be defined as [dictionaries](https://docs.python.org/3.9/library/stdt implementing a constructor. ## Handling parameters in controllers -The previous tutorial showed how request handlers support automatic injection -of parameters read from the HTTP request. Controllers support the same, -therefore it is possible to have parameters read automatically and injected -to controller methods: +The previous tutorial showed how request handlers support the automatic +injection of parameters read from the HTTP request. Controllers support the +same, therefore it is possible to have parameters read automatically and +injected into controller methods: ```python class Example(Controller): @@ -375,21 +375,21 @@ Controllers also support dependency injection for their constructor ## Serving static files This tutorial previously showed how the homepage of the MVC project template looks -like, at the root of the web site: +like, at the root of the website: ![MVC Project home](./img/mvc-template-home.png) The project template includes a folder for `static` files, including pictures, -CSS, JavaScript files. Static files are served using a catch-all route, reading -files whose path, relatively to the static folder, matches the URL path of the request. +CSS, and JavaScript files. Static files are served using a catch-all route, reading +files whose path, relative to the static folder, matches the URL path of the request. -For example, if the `static` folder contains such file: `scripts/example.js`, +For example, if the `static` folder contains the file `scripts/example.js`, web requests at `http://localhost:44777/scripts/example.js` will be resolved with this file and related information. When handling static files, BlackSheep automatically takes care of several details: -- it handles ETag response header, If-None-Match request header and HTTP 304 Not Modified - responses if files don't change on file system +- it handles the ETag response header, If-None-Match request header and HTTP 304 Not + Modified responses if files don't change on the file system - it handles HTTP GET requests returning file information - it handles Range requests, to support pause and restore downloads out of the box and enable optimal support for videos (videos can be downloaded from a certain @@ -401,22 +401,22 @@ browser. Relative paths are supported, but only files inside the root static folder are served, it is not possible to download files outside of the static folder (it would be a security issue if it worked otherwise!). -Additionally, BlackSheep only handles certain files extensions: by default +Additionally, BlackSheep only handles certain file extensions: by default only the most common file extensions used in web applications. Paths starting with "/" are always considered absolute paths starting from the -root of the web site. +root of the website. ## Strategy for application settings The `API` and the `MVC` project templates include a strategy to read and -validate application settings, from various sources, and supporting multiple -system environments (like `dev`, `test`, `prod` environments). +validate application settings, from various sources, and support multiple +system environments (like `dev`, `test`, and `prod` environments). - [`Pydantic`](https://docs.pydantic.dev/latest/) is always used to describe and validate application settings. - Application settings can be read from various sources using either `Pydantic v1 BaseSettings` class, or `essentials-configuration`. - When using `essentials-configuration`, use the `APP_ENV` environment variable - to control the application environment and to use environment specific + to control the application environment and to use environment-specific settings from dedicated files using the pattern: `settings.{{env_name}}.{{format}}`, like `settings.test.yaml`, `settings.prod.toml`. @@ -426,14 +426,14 @@ configuration depending on the application environment, refer to [_Settings_](/b ## Summary -This tutorial covered some higher level topics of a BlackSheep application. The -general concepts presented here apply to many kinds of web framework: +This tutorial covered some higher-level topics of a BlackSheep application. The +general concepts presented here apply to many kinds of web frameworks: - server side templating of HTML views - serving of static files - use of MVC architecture -The next pages describes the built-in support for +The next pages describe the built-in support for [dependency injection](../dependency-injection), and automatic generation of [OpenAPI Documentation](../openapi). diff --git a/docs/openapi.md b/docs/openapi.md index 3749e11..556c941 100644 --- a/docs/openapi.md +++ b/docs/openapi.md @@ -12,7 +12,7 @@ details. This page describes the following: - [X] How to implement a custom `UIProvider`. ## Introduction to OpenAPI Documentation -Citing from the [Swagger web site](https://swagger.io/specification/), at the +Citing from the [Swagger website](https://swagger.io/specification/), at the time of this writing: > The OpenAPI Specification (OAS) defines a standard {...} @@ -120,8 +120,8 @@ at `/openapi.json` path: ``` Note how the `Foo` component schema is automatically documented. `BlackSheep` -supports both `@dataclass` and `Pydantic` models for automatic generation of -documentation. +supports both `@dataclass` and `Pydantic` models for the automatic generation +of documentation. And also YAML format at `/openapi.yaml` path: @@ -208,8 +208,8 @@ async def home(): return "OpenAPI Example" ``` -When using `docstring`, the first line of the docstring is used as summary, -and the whole docstring as description. +When using `docstring`, the first line of the docstring is used as the summary, +and the whole docstring as the description. ![OpenAPI description and summary](./img/openapi-description-summary.png) @@ -233,7 +233,7 @@ async def hidden_endpoint(): ### Document only certain routes To document only certain routes, use an include function like in the example below. -For example, to include only those routes that starts with "/api": +For example, to include only those routes that start with "/api": ```python docs = OpenAPIHandler(info=Info(title="Example API", version="0.0.1")) @@ -271,7 +271,7 @@ class Cat: @docs( summary="Gets a cat by id", - description="""A sample API that uses a petstore as an + description="""A sample API that uses a pet store as an example to demonstrate features in the OpenAPI 3 specification""", responses={ 200: ResponseInfo( @@ -306,14 +306,14 @@ designed to support documenting responses with different content types (e.g. JSON, XML, etc.) and having examples for each content type. Writing the documentation by hand would be much more time consuming! -BlackSheep automatically generates components schemas by type (in this example, +BlackSheep automatically generates component schemas by type (in this example, `Cat`) and reuses them in all API endpoints that use them: ![OpenAPI Response Examples](./img/openapi-response-examples.png) ### Avoid code pollution using EndpointDocs -If you are familiar with other libraries to produce OpenAPI Documentation and +If you are familiar with other libraries that produce OpenAPI Documentation and you consider the example above, you might notice that adding OpenAPI details to request handlers can pollute the source code and distract the programmer from the actual request handlers' logic. @@ -339,7 +339,7 @@ documentation is organized and configured (in `app.docs`, `app.controllers.docs` ### Deprecating an API -To mark and endpoint as deprecated, use `@docs.deprecated()`: +To mark an endpoint as deprecated, use `@docs.deprecated()`: ```python @docs.deprecated() @@ -376,9 +376,9 @@ docs.bind_app(app) ### Handling common responses APIs often implement a common way to handle failures, to provide clients with -details for web requests that cannot complete successfully. For example, an API -might return a response body like the following, in case of a bad request for -a certain endpoint: +details for web requests that cannot be completed successfully. For example, an +API might return a response body like the following, in case of a bad request +for a certain endpoint: ```json {"error": "The provided country code is not supported", "code": "InvalidCountryCode"} @@ -437,16 +437,16 @@ Common responses are configured for all endpoints. ### Support for generics -The generation of OpenAPI Documentation supports handling of [generic +The generation of OpenAPI Documentation supports the handling of [generic types](https://docs.python.org/3/library/typing.html#typing.Generic). Consider the following example: 1. a common task is to implement an API that returns a paginated subset of elements, usually given some filters (e.g. textual search) -2. clients needs to know the count of items that match the filters, to display +2. clients need to know the count of items that match the filters, to display the total number of items and the number of pages that are necessary to display all results (depending on page size) -3. for such scenario, using a `Generic` type is a good solution, because many +3. for such a scenario, using a `Generic` type is a good solution, because many kinds of objects can be paginated _Example of generic class definition_ @@ -763,9 +763,10 @@ The BlackSheep package includes some static files to offer a good user experience in some circumstances. These include HTML pages used when enabling Swagger UI or ReDoc UI. -To control those pages, for example to alter the HTML structure or use different -sources for JavaScript and CSS files (which by the way could be the BlackSheep -application serving the OpenAPI specification files), it is recommended to: +To control those pages, for example, to alter the HTML structure or use +different sources for JavaScript and CSS files (which by the way could be the +BlackSheep application serving the OpenAPI specification files), it is +recommended to: - define a custom implementation of `UIProvider` - maintain the desired HTML file @@ -864,7 +865,7 @@ docs.bind_app(app) When OpenAPI Documentation is generated, operation ids are obtained from the name of the Python function definitions. -For example, having a `get_foo` request handler, generates an object having +For example, having a `get_foo` request handler generates an object having `operationId` equal to "get_foo": ```python @@ -896,8 +897,8 @@ class CustomOpenAPIHandler(OpenAPIHandler): ### For more details -For more details on the OpenAPI specification and understand some details such -as security settings, refer to the official [swagger.io web -site](https://swagger.io/specification/), and the dedicated library to generate -the specification file: +For more details on the OpenAPI specification and to understand some details +such as security settings, refer to the official [swagger.io +website](https://swagger.io/specification/), and the dedicated library to +generate the specification file: [essentials-openapi](https://github.com/Neoteroi/essentials-openapi). diff --git a/docs/openid-connect.md b/docs/openid-connect.md index 7262c76..c874ea9 100644 --- a/docs/openid-connect.md +++ b/docs/openid-connect.md @@ -78,7 +78,7 @@ async def home(user: Identity): | ------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app | Application | Instance of BlackSheep application. | | settings | OpenIDSettings | Instance of OpenIDSettings. | -| auth_handler | Optional[OpenIDTokensHandler] = None (CookiesOpenIDTokensHandler) | Instance of OpenIDTokensHandler that can handle tokens for requests and responses for the OpenID Connect flow. This class is responsible of communicating tokens to clients, and restoring tokens context for following requests. | +| auth_handler | Optional[OpenIDTokensHandler] = None (CookiesOpenIDTokensHandler) | Instance of OpenIDTokensHandler that can handle tokens for requests and responses for the OpenID Connect flow. This class is responsible for communicating tokens to clients, and restoring tokens context for following requests. | | parameters_builder | Optional[ParametersBuilder] = None | Optional instance of `ParametersBuilder`, used to handle parameters configured in redirects and requests to the authorization server. | | is_default | bool = True | If default, clients are automatically redirected to the `sign-in` page when a non-authenticated user tries to access in `GET` a web page that requires authentication. | @@ -93,15 +93,15 @@ The `OpenIDSettings` class has the following properties: | audience | Optional[str] = None | If specified, the `audience` for requests using scopes to an API ([ref.](https://auth0.com/docs/configure/apis/scopes/sample-use-cases-scopes-and-claims#request-custom-api-access)). | | client_secret | Optional[str] = None | For requests that use `Authorization Code Grant` flow, the secret of the client application in the identity server. | | discovery_endpoint | Optional[str] = None | If specified, the exact URL to the discovery point (useful with Okta when using custom scopes for an authorization server). | -| entry_path | str = "/sign-in" | The local entry-path for sign-in (this redirects to the sign-in page of the identity server). | +| entry_path | str = "/sign-in" | The local entry path for sign-in (this redirects to the sign-in page of the identity server). | | logout_path | str = "/sign-out" | The local path to the sign-out endpoint (this removes authentication cookie). | | post_logout_redirect_path | str = "/" | The local path to which a user is redirected after signing-out. | | callback_path | str = "/authorization-callback" | The local path to handle the redirect after a user signs-in (the reply_url in the identity server must be configured accordingly). | -| refresh_token_path | str = "/refresh-token" | The local path used to handle refresh tokens to obtain new tokens . | +| refresh_token_path | str = "/refresh-token" | The local path used to handle refresh tokens to obtain new tokens. | | scope | str = "openid profile email" | The scope of the request, by default an `id_token` is obtained with email and profile. | | response_type | str = "code" | Type of OAuth response. | -| redirect_uri | Optional[str] = None | If specified, the redirect URL that must match the one configured for the application. If not provided, a redirect_url is obtained automatically (see note 🗡️). | -| scheme_name | str = "OpenIDConnect" | The name of the authentication scheme, affecting the name of authentication cookies (see note 🍒). | +| redirect_uri | Optional[str] = None | If specified, the redirect URL that must match the one configured for the application. If not provided, a redirect_url is obtained automatically (see note 🗡️). | +| scheme_name | str = "OpenIDConnect" | The name of the authentication scheme, affecting the name of authentication cookies (see note 🍒). | | error_redirect_path | Optional[str] = None | If specified, the local path to which a user is redirected in case of error. | | end_session_endpoint | Optional[str] = None | If specified, the local path to which the user can log out. | @@ -119,7 +119,7 @@ Notes: ## Examples using custom scopes -An integration with a `Auth0` application that uses custom scopes, where the +An integration with an `Auth0` application that uses custom scopes, where the application obtains both an `id_token` and an `access_token` for an API, looks like the following: @@ -128,8 +128,8 @@ looks like the following: This example shows how to configure an OpenID Connect integration with Auth0, obtaining an id_token, an access_token, and a refresh_token. The id_token is exchanged with the client using a response cookie (also used to authenticate users -for following requests), while access token and the refresh token are not stored and -can only be accessed using optional events. +for following requests), while the access token and the refresh token are not stored +and can only be accessed using optional events. """ import uvicorn from blacksheep.server.application import Application @@ -144,7 +144,7 @@ secrets = Secrets.from_env() app = Application(show_error_details=True) -# Auth0 with custom scope +# Auth0 with a custom scope use_openid_connect( app, OpenIDSettings( @@ -252,7 +252,7 @@ _[Redis example](https://github.com/Neoteroi/BlackSheep-Examples/blob/main/oidc/ A concrete implementation is provided in `CookiesTokenStore`, storing tokens in cookies. It is possible to create custom implementations of the `TokensStore`, to use other mechanisms, -for example to store tokens in a Redis cache. +for example, to store tokens in a Redis cache. When a user is authenticated, and has an `access_token` (and/or a `refresh_token`), they are accessible through the `Identity`: @@ -281,13 +281,14 @@ the built-in `CookiesTokensStore`. Tokens that are stored in cookies are signed and encrypted using `itsdangerous`, with symmetric encryption. This means that BlackSheep applications need secrets to protect sensitive data. When keys are not specified, they are generated -automatically in memory, for best user's experience. +automatically in memory, for the best user experience. !!! danger This means that keys are not persisted when applications - restart, and not consistent when multiple instances of the same application - are deployed across regions, or within a same server. This is acceptable during - local development, but should not be the case in production environments. + restart, and are not consistent when multiple instances of the same + application are deployed across regions, or within the same server. This is + acceptable during local development, but should not be the case in + production environments. To use consistent keys, configure one or more environment variables like the following: diff --git a/docs/remotes.md b/docs/remotes.md index 874015c..e79ee0d 100644 --- a/docs/remotes.md +++ b/docs/remotes.md @@ -23,7 +23,7 @@ redirects, authentication, link generation when absolute URLs are needed, and client geolocation. This page documents how to configure BlackSheep to work with proxy servers and -load balancers, using provided classes to handle: +load balancers, using the provided classes to handle: - [X] X-Forwarded headers - [X] Forwarded header @@ -41,11 +41,11 @@ information about original web requests to web applications. | X-Forwarded-Host | Used to identify the original host requested by the client in the Host HTTP request header | | X-Forwarded-Proto | Used to identify the protocol (HTTP or HTTPS) that a client used to connect to your proxy or load balancer. | -BlackSheep provides a `XForwardedHeadersMiddleware` class to handle these +BlackSheep provides an `XForwardedHeadersMiddleware` class to handle these headers, providing: * optional validation of trusted hosts -* optional validation of proxies cound and IP addresses by known IPs or known +* optional validation of proxies count and IP addresses by known IPs or known networks To configure a BlackSheep web application to handle `X-Forwarded` headers and diff --git a/docs/request-handlers.md b/docs/request-handlers.md index ca04e9c..cc85518 100644 --- a/docs/request-handlers.md +++ b/docs/request-handlers.md @@ -1,7 +1,7 @@ # Request handlers The previous pages describe that a request handler in BlackSheep is a function -associated to a route, having the responsibility of handling web requests. +associated with a route, having the responsibility of handling web requests. This page describes `request handlers` in detail, covering the following: - [X] Request handler normalization. @@ -19,7 +19,7 @@ async def normal_handler(request: Request) -> Response: ``` -To be a request handler, a function must be associated to a route: +To be a request handler, a function must be associated with a route: ```python from blacksheep import Application, Request, Response, get, text @@ -35,12 +35,13 @@ async def normal_handler(request: Request) -> Response: A request handler defined this way is called directly to generate a response when a web request matches the route associated with the function (in this -case, HTTP GET on the root of the website "/"). +case, HTTP GET at the root of the website "/"). -However, to improve developer's experience and development speed, BlackSheep -implements automatic normalization of request handlers. For example it is -possible to define a request handler as a synchronous function, the framework -automatically wraps the synchronous function into an asynchronous wrapper: +However, to improve the developer's experience and development speed, +BlackSheep implements automatic normalization of request handlers. For example, +it is possible to define a request handler as a synchronous function, the +framework automatically wraps the synchronous function into an asynchronous +wrapper: ```python @@ -51,15 +52,15 @@ def sync_handler(request: Request) -> Response: ``` !!! danger "Avoid blocking code in synchronous methods!" - When a request handler is defined as synchronous method, BlackSheep + When a request handler is defined as a synchronous method, BlackSheep assumes that the author of the code knows what they are doing and about - asynchronous programming, and the response can be returned immediately - without I/O or CPU intensive operations that would block the event loop. - BlackSheep does nothing to prevent blocking the event loop, if you add - blocking operations in your code. + asynchronous programming, and that the response should be returned + immediately without I/O or CPU-intensive operations that would block the + event loop. BlackSheep does nothing to prevent blocking the event loop, if + you add blocking operations in your code. Similarly, request handlers are normalized when their function signature is -different than the normal one. For example a request handler can be defined +different than the normal one. For example, a request handler can be defined without arguments, and returning a plain `str` or an instance of an object (which gets serialized to `JSON` and configured as response content): @@ -91,9 +92,9 @@ def get_example_cat() -> Cat: ### Automatic binding of parameters -An important feature enabled by function normalization is automatic binding of -request parameters, as described in the `Getting Started` pages. Common -scenarios are using route parameters, and query string parameters: +An important feature enabled by function normalization is the automatic binding +of request parameters, as described in the `Getting Started` pages. Common +scenarios are using route parameters and query string parameters: ```python @@ -119,8 +120,8 @@ used. All examples so far showed how to use implicit binding of request parameters. In the `get_cats` example above, all parameters are _implicitly_ bound from the -request query string. To enable more scenarios, `BlackSheep` provides also -explicit bindings that let specifying the source of the parameter (e.g. +request query string. To enable more scenarios, `BlackSheep` also provides +explicit bindings that allow specifying the source of the parameter (e.g. request headers, cookies, route, query, body, application services). In the example below, `cat_input` is read automatically from the request payload as JSON and deserialized automatically into an instance of the `CreateCatInput` @@ -150,9 +151,9 @@ More details about bindings are described in _[Binders](../binders/)_. ### Normalization and OpenAPI Documentation -Request handler normalization enables also a more accurate generation of +Request handler normalization also enables a more accurate generation of [OpenAPI Documentation](../openapi/), since the web framework knows that request -handlers need input from query string, routes, headers, cookies, etc.; and +handlers need input from query strings, routes, headers, cookies, etc.; and produce responses of a certain type. ## Using asynchronous and synchronous code. @@ -160,7 +161,7 @@ produce responses of a certain type. BlackSheep supports both asynchronous and synchronous request handlers. Request handlers don't need to be asynchronous in those scenarios when the response is well-known and can be produced without doing any I/O bound operation or any -CPU intensive operation. This is the case for example of redirects, and the +CPU-intensive operation. This is the case for example of redirects, and the previous "Hello, There!" example: ```python @@ -180,14 +181,14 @@ def redirect_example() -> Response: ``` -Request handlers that do I/O bound operations or CPU intensive operations -should be instead `async`, to not hinder the performance of the web server. For +Request handlers that do I/O bound operations or CPU-intensive operations +should instead be `async`, to not hinder the performance of the web server. For example, if information is fetched from a database or a remote API when handling a web request handler, it is correct to use asynchronous code to reduce RAM consumption and not block the event loop of the web application. !!! warning - If an operation is CPU intensive (e.g. involving file operations, + If an operation is CPU-intensive (e.g. involving file operations, resizing a picture), the request handlers that initiate such operation should be async, and use a [thread or process pool](https://docs.python.org/3/library/asyncio-eventloop.html#executing-code-in-thread-or-process-pools) diff --git a/docs/requests.md b/docs/requests.md index c612f1e..1491866 100644 --- a/docs/requests.md +++ b/docs/requests.md @@ -7,15 +7,15 @@ This page describes: - [X] Reading request bodies. ## The Request class -BlackSheep handles requests as instances of `blacksheep.Request` -class. This class provides methods and properties to handle request headers, -cookies, URL, route parameters, request body, user's identity, and other +BlackSheep handles requests as instances of the `blacksheep.Request` class. +This class provides methods and properties to handle request headers, cookies, +the URL, route parameters, the request body, the user's identity, and other information like the content type of the request. Each web request results in the creation of a new instance of `Request`. ### Reading parameters from the request object It is possible to read query and route parameters from an instance of -`request`. The example below shows how query string, route parameters, and +`request`. The example below shows how the query string, route parameters, and request headers can be read from the request: ```python @@ -47,7 +47,7 @@ def example(request: Request) -> Response: However, the recommended approach is to use automatic bindings, which enable a more accurate generation of OpenAPI Documentation, automatic parsing of values -into the desired type, and improve development experience and source code. +into the desired type, and improves the development experience and source code. The same example can be achieved in the following way: @@ -111,7 +111,7 @@ def home(accept: FromAcceptHeader, foo: FromFooCookie) -> Response: ) ``` -### Reading request body +### Reading the request body The request class offers several methods to read request bodies of different kinds. @@ -140,16 +140,17 @@ kinds. ``` The type parameter for the `FromJSON` binder can be a dataclass, a model from - [`pydantic`](https://github.com/samuelcolvin/pydantic), a regular class with an - `__init__` method. + [`pydantic`](https://github.com/samuelcolvin/pydantic), or a regular class + with an `__init__` method. Note that when mapping the request's payload to an instance of the desired type, the type's constructor with `cls(**data)` is used. If it necessary to parse dates or other complex types this must be done in the constructor of the - class. To handle gracefully a payload with extra properties, use `*args` in - your class constructor: `__init__(one, two, three, *args)__`. + class. To gracefully handle a payload with extra properties, use `*args` in + your class constructor: `__init__(one, two, three, *args)`. - To read the JSON payload as a regular dictionary, use `dict` as type argument: + To read the JSON payload as a regular dictionary, use `dict` as the type + argument: ```python @post("/something") @@ -170,7 +171,7 @@ kinds. # data is the deserialized object ``` -#### Reading form +#### Reading a form request body === "Using binders (recommended)" @@ -191,8 +192,8 @@ kinds. async def create_something(input: FromForm[SomethingInput]): data = input.value - # data is already deserialized from form into an instance of - # `SomethingInput` - however some properties need to be parsed + # data is already deserialized from the form body into an instance + # of `SomethingInput` - however some properties need to be parsed # from str into the desired type in the class definition - # see __init__ above ``` @@ -283,7 +284,7 @@ Files read from `multipart/form-data` payload. ``` #### Reading streams -Reading streams enables reading bodies of big size using asynchronous +Reading streams enables reading large-sized bodies using an asynchronous generator. The example below saves a file of arbitrary size without blocking the event loop: diff --git a/docs/responses.md b/docs/responses.md index 7700961..49149be 100644 --- a/docs/responses.md +++ b/docs/responses.md @@ -8,10 +8,10 @@ This page describes: ## The Response class A normal request handler in BlackSheep is expected to return an instance of the `blacksheep.Response` class. Users of the framework can define -request handlers that return different kinds of objects, in such case they are -normalized at application start-up, to return instances of `Response`. +request handlers that return different kinds of objects. In such cases they are +normalized at application start-up to return instances of `Response`. -The following example shows how to use the low level objects to create a +The following example shows how to use the low-level objects to create a response with status 200 and body "Hello, World": ```python @@ -28,9 +28,9 @@ def home() -> Response: `BlackSheep` uses these exact types to benefit from static typing and compilation of [`Cython` extensions](https://cython.org). However, handling -responses this way is not comfortable for regular use. For this reason a number -of helper functions are provided to create `Response` objects with a simpler -code API. +responses this way is not comfortable for regular use. For this reason, a +number of helper functions are provided to create `Response` objects with a +simpler code API. For example, the `json` function in `blacksheep.server.responses` produces a response object having a JSON body. @@ -79,29 +79,29 @@ if necessary. The table below describes the built-in functions to produce responses: -| Method | Description | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **ok** | Returns an HTTP 200 OK response, with optional message; sent as plain text or JSON. | -| **status_code** | Returns a plain response with given status, with optional message; sent as plain text or JSON. | -| **created** | Returns an HTTP 201 Created response, to the given location and with optional JSON content. | -| **accepted** | Returns an HTTP 202 Accepted response, with optional message; sent as plain text or JSON. | -| **no_content** | Returns an HTTP 204 No Content response. | -| **json** | Returns a response with application/json content, and given status (default HTTP 200 OK). | -| **pretty_json** | Returns a response with indented application/json content, and given status (default HTTP 200 OK). | -| **text** | Returns a response with text/plain content, and given status (default HTTP 200 OK). | -| **moved_permanently** | Returns an HTTP 301 Moved Permanently response, to the given location. | -| **redirect** | Returns an HTTP 302 Found response (commonly called redirect), to the given location. | -| **see_other** | Returns an HTTP 303 See Other response, to the given location. | -| **not_modified** | Returns an HTTP 304 Not Modified response. | -| **temporary_redirect** | Returns an HTTP 307 Temporary Redirect response, to the given location. | -| **permanent_redirect** | Returns an HTTP 308 Permanent Redirect response, to the given location. | -| **bad_request** | Returns an HTTP 400 Bad Request response, with optional message; sent as plain text or JSON. | -| **unauthorized** | Returns an HTTP 401 Unauthorized response, with optional message; sent as plain text or JSON. | -| **forbidden** | Returns an HTTP 403 Forbidden response, with optional message; sent as plain text or JSON. | -| **not_found** | Returns an HTTP 404 Not Found response, with optional message; sent as plain text or JSON. | -| **view** | Returns a view rendered synchronously. | -| **view_async** | Returns a view rendered asynchronously. | -| **file** | Returns a binary file response with given content type and optional file name, for download (attachment) (default HTTP 200 OK). This method supports both call with bytes, or a generator yielding chunks. | +| Method | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **ok** | Returns an HTTP 200 OK response, with an optional message; sent as plain text or JSON. | +| **status_code** | Returns a plain response with the given status, and with an optional message; sent as plain text or JSON. | +| **created** | Returns an HTTP 201 Created response, to the given location and with optional JSON content. | +| **accepted** | Returns an HTTP 202 Accepted response, with an optional message; sent as plain text or JSON. | +| **no_content** | Returns an HTTP 204 No Content response. | +| **json** | Returns a response with application/json content, and the given status (default HTTP 200 OK). | +| **pretty_json** | Returns a response with indented application/json content, and the given status (default HTTP 200 OK). | +| **text** | Returns a response with text/plain content, and the given status (default HTTP 200 OK). | +| **moved_permanently** | Returns an HTTP 301 Moved Permanently response, to the given location. | +| **redirect** | Returns an HTTP 302 Found response (commonly called redirect), to the given location. | +| **see_other** | Returns an HTTP 303 See Other response, to the given location. | +| **not_modified** | Returns an HTTP 304 Not Modified response. | +| **temporary_redirect** | Returns an HTTP 307 Temporary Redirect response, to the given location. | +| **permanent_redirect** | Returns an HTTP 308 Permanent Redirect response, to the given location. | +| **bad_request** | Returns an HTTP 400 Bad Request response, with an optional message; sent as plain text or JSON. | +| **unauthorized** | Returns an HTTP 401 Unauthorized response, with an optional message; sent as plain text or JSON. | +| **forbidden** | Returns an HTTP 403 Forbidden response, with an optional message; sent as plain text or JSON. | +| **not_found** | Returns an HTTP 404 Not Found response, with an optional message; sent as plain text or JSON. | +| **view** | Returns a view rendered synchronously. | +| **view_async** | Returns a view rendered asynchronously. | +| **file** | Returns a binary file response with the given content type and optional file name, for download (attachment) (default HTTP 200 OK). This method supports being called with bytes, or a generator yielding chunks. | For information on how to use these methods, refer to the type annotations provided in the code. @@ -124,7 +124,7 @@ def home(): response.add_header(b"Example", b"Value") - # add supports multiple headers with same name: + # add supports multiple headers with the same name: response.headers.add(b"X-Foo", b"Foo") # set syntax overrides other headers with the same name: diff --git a/docs/routing.md b/docs/routing.md index 1397f94..fb0b068 100644 --- a/docs/routing.md +++ b/docs/routing.md @@ -1,9 +1,9 @@ # Routing Server side routing refers to the ability of a web application to handle web -requests using different functions, depending on URL path and HTTP method. Each -`BlackSheep` application is bound to a router, which provides several ways to -define routes. A function that is bound to a route is called "request +requests using different functions, depending on the URL path and HTTP method. +Each `BlackSheep` application is bound to a router, which provides several ways +to define routes. A function that is bound to a route is called a "request handler", since its responsibility is to handle web requests and produce responses. @@ -140,7 +140,7 @@ BlackSheep supports three ways to define route parameters: * `"/"` - using angle brackets (i.e. [Flask notation](https://flask.palletsprojects.com/en/1.1.x/quickstart/?highlight=routing#variable-rules)) Route parameters can be read from `request.route_values`, or injected -automatically by request handler's function signature: +automatically by the request handler's function signature: ```python @@ -252,7 +252,7 @@ And then use it in routes: ## Catch-all routes To define a catch-all route that will match every request, use a route -parameter with path value pattern, like: +parameter with a path value pattern, like: * `{path:name}`, or `` @@ -269,9 +269,9 @@ For example, a request at `/catch-all/anything/really.js` would be matched by the route above, and the `sub_path` value would be `anything/really.js`. It is also possible to define a catch-all route using a star sign `*`. To read -the portion of the path catched by the star sign from the request object, read +the portion of the path caught by the star sign from the request object, read the "tail" property of `request.route_values`. But in this case the value of the -catched path can only be read from the request object. +caught path can only be read from the request object. ```python @@ -298,8 +298,8 @@ app.router.fallback = fallback The `Router` class supports filters for routes and sub-routers. In the following example, a web request for the root of the service "/" having a request header -"X-Area" == "Test" gets the reply of the `test_home` request handler, without -such header the reply of the `home` request handler. +"X-Area" == "Test" gets the reply of the `test_home` request handler, and +without such header the reply of the `home` request handler is returned. ```python from blacksheep import Application, Router @@ -325,8 +325,8 @@ app = Application(router=router) A router can have filters based on headers, host name, query string parameters, and custom user-defined filters. -Query string filters can be defined using the `params` parameter, by host using -the `host` parameter: +Query string filters can be defined using the `params` parameter, and host name +filters can be defined using the `host` parameter: ```python filter_by_query = Router(params={"version": "1"}) @@ -378,14 +378,14 @@ class Home(Controller): ... ``` -In this case routes are registered using default singleton routers, used if an +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: +# This application uses the default singleton routers exposed by BlackSheep: app = Application() ``` @@ -456,8 +456,8 @@ 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: +`blacksheep.server.routing.RoutesRegistry`. If using a dedicated router for +controllers is desired, do this instead: ```python # app/controllers.py @@ -501,6 +501,7 @@ 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. + + Controller routes use a "RoutesRegistry" to support the dynamic generation + of paths by controller class name. Controller routes are evaluated and + merged into `Application.router` when the application starts. diff --git a/docs/settings.md b/docs/settings.md index e7863e8..f451a7e 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -4,7 +4,7 @@ While _most_ settings are described in sections that are dedicated to other topics, this page describes other settings that can be used in BlackSheep. This page describes: -- [X] features to describe the environment of a BlackSheep web applications. +- [X] features to describe the environment of a BlackSheep web application. - [X] features to control JSON serialization and deserialization ## Environmental variables @@ -14,8 +14,8 @@ This page describes: | APP_ENV | Settings | This environment variable is read to determine the environment of the application. For more information, refer to [_Defining application environment_](/blacksheep/settings/#defining-application-environment). | | APP_SHOW_ERROR_DETAILS | Settings | If "1" or "true", configures the application to display web pages with error details in case of HTTP 500 Internal Server Error. | | APP_MOUNT_AUTO_EVENTS | Settings | If "1" or "true", automatically binds lifecycle events of mounted apps between children and parents BlackSheep applications. | -| APP_SECRET_i | Secrets | Allows to configure the secrets used by the application to protect data. | -| BLACKSHEEP_SECRET_PREFIX | Secrets | Allows to specify the prefix of environment variables used to configure application secrets, defaults to "APP_SECRET" if not specified. | +| APP_SECRET_i | Secrets | Allows configuring the secrets used by the application to protect data. | +| BLACKSHEEP_SECRET_PREFIX | Secrets | Allows specifying the prefix of environment variables used to configure application secrets, defaults to "APP_SECRET" if not specified. | ## Defining application environment @@ -29,7 +29,7 @@ the environment. For example: folder. The module `blacksheep.server.env` offers two functions that can be used to -control behavior depending on app environment: +control behavior depending on the app environment: | Function | True if `APP_ENV` is... | Description | | ---------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | @@ -71,13 +71,13 @@ from blacksheep.plugins import json def custom_loads(value): """ - This function is responsible of parsing JSON into instances of objects. + This function is responsible for parsing JSON into instances of objects. """ def custom_dumps(value): """ - This function is responsible of creating JSON representations of objects. + This function is responsible for creating JSON representations of objects. """ @@ -128,7 +128,7 @@ fee of the superfluous `decode -> encode` passage, it is recommended to: * not use the built-in `responses` functions and the built-in `JSONContent` class -* use a custom defined function for JSON responses like the following example: +* use a custom-defined function for JSON responses like the following example: ```python def my_json(data: Any, status: int = 200) -> Response: diff --git a/docs/static-files.md b/docs/static-files.md index c171faf..038b6b2 100644 --- a/docs/static-files.md +++ b/docs/static-files.md @@ -53,8 +53,8 @@ app.serve_files("app/videos", root_path="videos") ``` ## File extensions -Only files with configured extension are served to the client. By default, only -files with these extensions are served (case insensitive check): +Only files with a configured extension are served to the client. By default, +only files with these extensions are served (case insensitive check): ```python '.txt', @@ -84,7 +84,7 @@ app.serve_files("static", extensions={'.foo', '.config'}) ## Accept-Ranges and Range requests Range requests are enabled and handled by default (since version `0.2.1`), -meaning that BlackSheep supports serving big files with pause and resume +meaning that BlackSheep supports serving big files with the pause and resume feature, and serving videos with the possibility to jump to specific points. ## ETag and If-None-Match @@ -117,8 +117,8 @@ app.serve_files( ) ``` -If the SPA uses a file with a different name, specify both index file name and -fallback document to be the same: +If the SPA uses a file with a different name, specify both the index file name +and the fallback document to be the same: ```python diff --git a/docs/templating.md b/docs/templating.md index b39641a..2aaa578 100644 --- a/docs/templating.md +++ b/docs/templating.md @@ -82,7 +82,7 @@ async def home(): It is possible to load templates by name including '.jinja', or without file extension; '.jinja' extension is added automatically. The extension must be -lower case. +lowercase. ```python @get("/") @@ -167,9 +167,9 @@ class CustomRenderer(Renderer): """Renders a view asynchronously.""" ... - def bind_antiforgery_handler(self, handler: AntiForgeryHandler) -> None: + def bind_anti_forgery_handler(self, handler: AntiForgeryHandler) -> None: """ - Applies extensions for an antiforgery handler. + Applies extensions for an anti-forgery handler. This method can be used to generate HTML fragments containing anti-forgery tokens, for the built-in implementation of AF validation. diff --git a/docs/testing.md b/docs/testing.md index 8ebe5a7..7d0c1cb 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -78,7 +78,7 @@ A test client provides the following methods: !!! info By default a `TestClient` simulates web requests creating `ASGI` scopes, - however it can be configured with a custom implementation of + however, it can be configured with a custom implementation of `AbstractTestSimulator` to generate real HTTP requests against a real HTTP server. For simplicity, this is not documented here. @@ -199,7 +199,7 @@ from app.main import app ### Navigating the API using OpenAPI Documentation -At this point the application can already be started. The API methods are not +At this point, the application can already be started. The API methods are not implemented, yet, so they cannot do anything interesting. However, the framework can generate OpenAPI Documentation and expose a documentation UI. @@ -420,7 +420,7 @@ pytest ``` If the steps above were done correctly, the test succeeds and `pytest` produces -a feedback like in the following picture (using the verbose flag `-v`): +feedback like in the following picture (using the verbose flag `-v`): ![pytest run](./img/pytest-tests.png) @@ -508,7 +508,7 @@ def session_two(server_host, server_port_two): ``` And a full example for the TODO API described in the tutorial could look like -the following (the example requires `requests` library): +the following (the example requires the `requests` library): ```python import os diff --git a/docs/versions/migrating-to-v2.md b/docs/versions/migrating-to-v2.md index e006aa2..fceeec0 100644 --- a/docs/versions/migrating-to-v2.md +++ b/docs/versions/migrating-to-v2.md @@ -54,7 +54,7 @@ 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 +automatically in v2. Automatic import works relative to where a BlackSheep application is instantiated. In the structure described below, the modules in `app.controllers` and `app.routes` namespace are imported automatically when an application is instantiated inside `app.main`. @@ -72,13 +72,13 @@ app/ └──main.py ``` -The difference in code verbosity is considerable, because previously definining +The difference in code verbosity is considerable, because previously defining routes and controllers explicitly was not sufficient to have them registered in applications. ## Notion of application environment -The namespace `blacksheep.server.env` provide an abstraction layer to support +The namespace `blacksheep.server.env` provides an abstraction layer to support the notion of _application environment_. It provides functions that can be used to apply logic depending on whether the application is running for local development, or a different kind of environment (e.g. `dev`, `test`, `prod`). @@ -105,8 +105,8 @@ For more information, read the [_dedicated part in the Dependency Injection_](/b ## Changes to server side rendering -`BlackSheep` v2 has been modified to not be strictly related to `Jinja2` for -templates rendering. To achieve this, two new namespaces have been added: +`BlackSheep` v2 has been modified to not be strictly reliant on `Jinja2` for +template rendering. To achieve this, two new namespaces have been added: - `blacksheep.server.rendering.abc`, defining an abstract `Renderer` class, - `blacksheep.settings.html`, defining a code API to control renderer settings @@ -324,25 +324,25 @@ The full list of changes in alpha versions released for `v2`: - Fixes bug #305 (`ClientSession ssl=False` not working as intended). - Refactors the classes for OpenID Connect integration to support alternative ways to share tokens with clients, and JWT Bearer token authentication out - of the box, in alternative to cookie based authentication. + of the box, in alternative to cookie-based authentication. - It adds built-in support for storing tokens (`id_token`, `access_token`, and - `refresh_token`) using the HTML5 Storage API (supportin `localStorage` and + `refresh_token`) using the HTML5 Storage API (supporting `localStorage` and `sessionStorage`). Refresh tokens, if present, are automatically protected to prevent leaking. See [the OIDC examples](https://github.com/Neoteroi/BlackSheep-Examples/tree/main/oidc) for more information. - Renames `blacksheep.server.authentication.oidc.TokensStore` to `TokensStore`. - Removes the `tokens_store` parameter from the `use_openid_connect` method; - it is still available as optional parameter of the two built-in classes used - to handle tokens. + it is still available as an optional parameter of the two built-in classes + used to handle tokens. - Replaces `request.identity` with `request.user`. The property `identity` is still kept for backward compatibility, but it will be removed in `v3`. - Removes 'HtmlContent' and 'JsonContent' that were kept as alternative names for `HTMLContent` and `JSONContent`. - Refactors the `ClientSession` to own by default a connections pool, if none - is specified for it. The connections pool is automatically disposed when the - client is exited, if it was created for the client. -- Makes the `ClientSession` more user friendly, supporting headers defined as + is specified for it. The connections pool is automatically disposed of when + the client is exited, if it was created for the client. +- Makes the `ClientSession` more user-friendly, supporting headers defined as `dict[str, str]` or `list[tuple[str, str]]`. - Improves the type annotations of the `ClientSession`. - Corrects a bug in the `ClientSession` that would cause a task lock when the @@ -359,7 +359,7 @@ The full list of changes in alpha versions released for `v2`: - Corrects the `Request` class to not generate more than one `Cookie` header when multiple cookies are set, to [respect the specification](https://www.rfc-editor.org/rfc/rfc6265#section-5.4). - Adds `@app.lifespan` to support registering objects that must be initialized - at application start, and disposed at application shutdown. + at application start, and disposed of at application shutdown. The solution supports registering as many objects as desired. - Adds features to handle `cache-control` response headers: a decorator for request handlers and a middleware to set a default value for all `GET` @@ -387,7 +387,7 @@ async def register_http_client(): app.services.register(ClientSession, instance=client) yield - print("HTTP client disposed") + print("HTTP client disposed of") @router.get("/") @@ -402,7 +402,7 @@ if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=44777, log_level="debug", lifespan="on") ``` -- Adds support for user defined filters for server routes (`RouteFilter` class). +- Adds support for user-defined filters for server routes (`RouteFilter` class). - Adds built-in support for routing based on request headers. - Adds built-in support for routing based on request query parameters. - Adds built-in support for routing based on host header value. @@ -413,8 +413,8 @@ if __name__ == "__main__": - Adds `Cache-Control: no-cache, no-store' to all responses generated for the OpenID Connect flow. - Adds support for automatic import of modules defined under `controllers` and - `routes` packages, relatively to where the `Application` class is - instantiated. Fix #334. + `routes` packages, relative to where the `Application` class is instantiated. + Fix #334. - Adds a `GzipMiddleware` that can be used to enable `gzip` compression, using the built-in module. Contributed by @tyzhnenko :sparkles: - Improves how tags are generated for OpenAPI Documentation: adds the @@ -455,7 +455,7 @@ if __name__ == "__main__": - Fix [#371](https://github.com/Neoteroi/BlackSheep/issues/371). Returns status 403 Forbidden when the user is authenticated but not authorized to perform an action. -- Fixes `TypeError` when writing a request without host header. +- Fixes `TypeError` when writing a request without a host header. - Add support for `Pydantic` `v2`: meaning feature parity with support for Pydantic v1 (generating OpenAPI Documentation). - Add support for `Union` types in sub-properties of request handlers input and diff --git a/docs/websocket.md b/docs/websocket.md index de5c07b..ef9d2cc 100644 --- a/docs/websocket.md +++ b/docs/websocket.md @@ -48,7 +48,7 @@ You can use route parameters just like with the regular request handlers. ``` A `WebSocket` object will be bound to a parameter injected into your handler -function when the client will try to connect to the endpoint. +function when the client tries to connect to the endpoint. !!! warning "Be careful" Make sure that your function either has a parameter named **websocket** or @@ -97,7 +97,7 @@ client sends you encoded JSON strings. Below is a simple example of an echo WebSocket handler. This function will receive a text message sent by the client and echo it back -until either the client disconnects or the server shut down. +until either the client disconnects or the server shuts down. === "Text" @@ -141,8 +141,8 @@ until either the client disconnects or the server shut down. ## Handling client disconnect -In event of client disconnect, the ASGI server will close the connection and -send the corresponding message to your app. Upon receiving this message +In the event of a client disconnect, the ASGI server will close the connection +and send the corresponding message to your app. Upon receiving this message `WebSocket` object will raise the `WebSocketDisconnectError` exception. You'll likely want to catch it and handle it somehow.